X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=ndmp-src%2Fndmpconnobj.c;fp=ndmp-src%2Fndmpconnobj.c;h=d5a5bf2bb3b7677bfb4558e4be7644727578c187;hb=fd48f3e498442f0cbff5f3606c7c403d0566150e;hp=0000000000000000000000000000000000000000;hpb=96f35b20267e8b1a1c846d476f27fcd330e0b018;p=debian%2Famanda diff --git a/ndmp-src/ndmpconnobj.c b/ndmp-src/ndmpconnobj.c new file mode 100644 index 0000000..d5a5bf2 --- /dev/null +++ b/ndmp-src/ndmpconnobj.c @@ -0,0 +1,811 @@ +/* + * Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contact information: Zmanda Inc., 465 N Mathlida Ave, Suite 300 + * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com + */ + +#include "amanda.h" +#include "ndmpconnobj.h" + +/* + * NDMPConnection class implementation + */ + +/* level at which to snoop when VERBOSE is set; 8 = everything but hexdumps, + * and 5 = packets without details */ +#define SNOOP_LEVEL 7 + +static GObjectClass *parent_class = NULL; + +/* and equipment to ensure we only talk to ndmlib in one thread at a time, even + * using multiple connections. The ndmlib code is not necessarily reentrant, + * so this is better safe than sorry. */ +static GStaticMutex ndmlib_mutex = G_STATIC_MUTEX_INIT; + +/* macros like those in ndmlib.h, but designed for use in this class */ +/* (copied from ndmp-src/ndmlib.h; see that file for copyright and license) */ + +#define NDMP_TRANS(SELF, TYPE) \ + { \ + struct ndmp_xa_buf * xa = &(SELF)->conn->call_xa_buf; \ + TYPE##_request * request; \ + TYPE##_reply * reply; \ + request = &xa->request.body.TYPE##_request_body; \ + reply = &xa->reply.body.TYPE##_reply_body; \ + NDMOS_MACRO_ZEROFILL (xa); \ + xa->request.protocol_version = NDMP4VER; \ + xa->request.header.message = (ndmp0_message) MT_##TYPE; \ + g_static_mutex_lock(&ndmlib_mutex); \ + { + +#define NDMP_TRANS_NO_REQUEST(SELF, TYPE) \ + { \ + struct ndmp_xa_buf * xa = &(SELF)->conn->call_xa_buf; \ + TYPE##_reply * reply; \ + reply = &xa->reply.body.TYPE##_reply_body; \ + NDMOS_MACRO_ZEROFILL (xa); \ + xa->request.protocol_version = NDMP4VER; \ + xa->request.header.message = (ndmp0_message) MT_##TYPE; \ + g_static_mutex_lock(&ndmlib_mutex); \ + { + +#define NDMP_CALL(SELF) \ + do { \ + (SELF)->last_rc = (*(SELF)->conn->call)((SELF)->conn, xa); \ + if ((SELF)->last_rc) { \ + NDMP_FREE(); \ + g_static_mutex_unlock(&ndmlib_mutex); \ + return FALSE; \ + } \ + } while (0); + +#define NDMP_FREE() ndmconn_free_nmb(NULL, &xa->reply) + +#define NDMP_END \ + g_static_mutex_unlock(&ndmlib_mutex); \ + } } + +/* + * Methods + */ + +static void +finalize_impl(GObject *goself) +{ + NDMPConnection *self = NDMP_CONNECTION(goself); + + /* chain up first */ + G_OBJECT_CLASS(parent_class)->finalize(goself); + + g_debug("closing conn#%d", self->connid); + + /* close this connection if necessary */ + if (self->conn) { + ndmconn_destruct(self->conn); + self->conn = NULL; + } + + if (self->log_state) { + g_free(self->log_state); + self->log_state = NULL; + } +} + +/* + * Error handling + */ + +ndmp4_error +ndmp_connection_err_code( + NDMPConnection *self) +{ + if (self->startup_err) { + return NDMP4_IO_ERR; + } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) { + return self->conn->last_reply_error; + } else { + return NDMP4_NO_ERR; + } +} + +gchar * +ndmp_connection_err_msg( + NDMPConnection *self) +{ + if (self->startup_err) { + return g_strdup(self->startup_err); + } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) { + return g_strdup_printf("Error from NDMP server: %s", + ndmp9_error_to_str(self->conn->last_reply_error)); + } else if (self->last_rc) { + return g_strdup_printf("ndmconn error %d: %s", + self->last_rc, ndmconn_get_err_msg(self->conn)); + } else { + return g_strdup_printf("No error"); + } +} + +static void +ndmp_connection_ndmlog_deliver( + struct ndmlog *log, + char *tag, + int lev G_GNUC_UNUSED, + char *msg) +{ + NDMPConnection *self = NDMP_CONNECTION(log->cookie); + g_debug("conn#%d: %s: %s", self->connid, tag, msg); +} + +void +ndmp_connection_set_verbose( + NDMPConnection *self, + gboolean verbose) +{ + struct ndmlog *device_ndmlog; + g_assert(!self->startup_err); + + device_ndmlog = g_new0(struct ndmlog, 1); + + self->log_state = (gpointer)device_ndmlog; + device_ndmlog->deliver = ndmp_connection_ndmlog_deliver; + device_ndmlog->cookie = self; + + if (verbose) { + ndmconn_set_snoop(self->conn, + device_ndmlog, + SNOOP_LEVEL); + } else { + ndmconn_clear_snoop(self->conn); + } +} + +/* + * Operations + */ + +gboolean +ndmp_connection_scsi_open( + NDMPConnection *self, + gchar *device) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_scsi_open) + request->device = device; + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_scsi_close( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_scsi_close) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_scsi_execute_cdb( + NDMPConnection *self, + guint32 flags, /* NDMP4_SCSI_DATA_{IN,OUT}; OUT = to device */ + guint32 timeout, /* in ms */ + gpointer cdb, + gsize cdb_len, + gpointer dataout, + gsize dataout_len, + gsize *actual_dataout_len, /* output */ + gpointer datain, /* output */ + gsize datain_max_len, /* output buffer size */ + gsize *actual_datain_len, /* output */ + guint8 *status, /* output */ + gpointer ext_sense, /* output */ + gsize ext_sense_max_len, /* output buffer size */ + gsize *actual_ext_sense_len /* output */ + ) +{ + g_assert(!self->startup_err); + + if (status) + *status = 0; + if (actual_dataout_len) + *actual_dataout_len = 0; + if (actual_datain_len) + *actual_datain_len = 0; + if (actual_ext_sense_len) + *actual_ext_sense_len = 0; + + NDMP_TRANS(self, ndmp4_scsi_execute_cdb) + request->flags = flags; + request->timeout = timeout; + request->datain_len = datain_max_len; + request->cdb.cdb_len = cdb_len; + request->cdb.cdb_val = cdb; + request->dataout.dataout_len = dataout_len; + request->dataout.dataout_val = dataout; + + NDMP_CALL(self); + + if (status) + *status = reply->status; + if (actual_dataout_len) + *actual_dataout_len = reply->dataout_len; + + reply->datain.datain_len = MIN(datain_max_len, reply->datain.datain_len); + if (actual_datain_len) + *actual_datain_len = reply->datain.datain_len; + if (datain_max_len && datain) + g_memmove(datain, reply->datain.datain_val, reply->datain.datain_len); + + reply->ext_sense.ext_sense_len = MIN(ext_sense_max_len, reply->ext_sense.ext_sense_len); + if (actual_ext_sense_len) + *actual_ext_sense_len = reply->ext_sense.ext_sense_len; + if (ext_sense_max_len && ext_sense) + g_memmove(ext_sense, reply->ext_sense.ext_sense_val, reply->ext_sense.ext_sense_len); + + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_open( + NDMPConnection *self, + gchar *device, + ndmp9_tape_open_mode mode) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_tape_open) + request->device = device; + request->mode = mode; + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_close( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_close) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_mtio( + NDMPConnection *self, + ndmp9_tape_mtio_op tape_op, + gint count, + guint *resid_count) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_tape_mtio) + request->tape_op = tape_op; + request->count = count; + NDMP_CALL(self); + *resid_count = reply->resid_count; + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_write( + NDMPConnection *self, + gpointer buf, + guint64 len, + guint64 *count) +{ + g_assert(!self->startup_err); + + *count = 0; + + NDMP_TRANS(self, ndmp4_tape_write) + request->data_out.data_out_val = buf; + request->data_out.data_out_len = len; + NDMP_CALL(self); + *count = reply->count; + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_read( + NDMPConnection *self, + gpointer buf, + guint64 count, + guint64 *out_count) +{ + g_assert(!self->startup_err); + + *out_count = 0; + + NDMP_TRANS(self, ndmp4_tape_read) + request->count = count; + NDMP_CALL(self); + *out_count = reply->data_in.data_in_len; + g_memmove(buf, reply->data_in.data_in_val, *out_count); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_tape_get_state( + NDMPConnection *self, + guint64 *blocksize, + guint64 *file_num, + guint64 *blockno) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_get_state) + NDMP_CALL(self); + + if (reply->unsupported & NDMP4_TAPE_STATE_BLOCK_SIZE_UNS) + *blocksize = 0; + else + *blocksize = reply->block_size; + + if (reply->unsupported & NDMP4_TAPE_STATE_FILE_NUM_UNS) + *file_num = G_MAXUINT64; + else + *file_num = reply->file_num; + + if (reply->unsupported & NDMP4_TAPE_STATE_BLOCKNO_UNS) + *blockno = G_MAXUINT64; + else + *blockno = reply->blockno; + + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_set_record_size( + NDMPConnection *self, + guint32 record_size) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_mover_set_record_size) + /* this field is "len" in ndmp4, but "record_size" in ndmp9 */ + request->len = record_size; + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_set_window( + NDMPConnection *self, + guint64 offset, + guint64 length) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_mover_set_window) + request->offset = offset; + request->length = length; + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_read( + NDMPConnection *self, + guint64 offset, + guint64 length) +{ + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_mover_read) + request->offset = offset; + request->length = length; + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_continue( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_continue) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_listen( + NDMPConnection *self, + ndmp9_mover_mode mode, + ndmp9_addr_type addr_type, + DirectTCPAddr **addrs) +{ + unsigned int naddrs, i; + *addrs = NULL; + + g_assert(!self->startup_err); + + NDMP_TRANS(self, ndmp4_mover_listen) + request->mode = mode; + request->addr_type = addr_type; + NDMP_CALL(self); + + if (request->addr_type != reply->connect_addr.addr_type) { + g_warning("MOVER_LISTEN addr_type mismatch; got %d", reply->connect_addr.addr_type); + } + + if (reply->connect_addr.addr_type == NDMP4_ADDR_TCP) { + naddrs = reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_len; + *addrs = g_new0(DirectTCPAddr, naddrs+1); + for (i = 0; i < naddrs; i++) { + ndmp4_tcp_addr *na = &reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_val[i]; + (*addrs)[i].ipv4 = na->ip_addr; + (*addrs)[i].port = na->port; + } + } + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_abort( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_abort) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_stop( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_stop) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean +ndmp_connection_mover_close( + NDMPConnection *self) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_close) + NDMP_CALL(self); + NDMP_FREE(); + NDMP_END + return TRUE; +} + +gboolean ndmp_connection_mover_get_state( + NDMPConnection *self, + ndmp9_mover_state *state, + guint64 *bytes_moved, + guint64 *window_offset, + guint64 *window_length) +{ + g_assert(!self->startup_err); + + NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_get_state) + NDMP_CALL(self); + if (state) *state = reply->state; + if (bytes_moved) *bytes_moved = reply->bytes_moved; + if (window_offset) *window_offset = reply->window_offset; + if (window_length) *window_length = reply->window_length; + NDMP_FREE(); + NDMP_END + return TRUE; +} + +static gboolean +ndmconn_handle_notify( + NDMPConnection *self, + struct ndmp_msg_buf *nmb) +{ + g_assert(!self->startup_err); + + if (nmb->header.message_type == NDMP0_MESSAGE_REQUEST) { + switch (nmb->header.message) { + case NDMP4_NOTIFY_DATA_HALTED: { + ndmp4_notify_data_halted_post *post = + &nmb->body.ndmp4_notify_data_halted_post_body; + self->data_halt_reason = post->reason; + break; + } + + case NDMP4_NOTIFY_MOVER_HALTED: { + ndmp4_notify_mover_halted_post *post = + &nmb->body.ndmp4_notify_mover_halted_post_body; + self->mover_halt_reason = post->reason; + break; + } + + case NDMP4_NOTIFY_MOVER_PAUSED: { + ndmp4_notify_mover_paused_post *post = + &nmb->body.ndmp4_notify_mover_paused_post_body; + self->mover_pause_reason = post->reason; + self->mover_pause_seek_position = post->seek_position; + break; + } + + case NDMP4_LOG_FILE: + case NDMP4_LOG_MESSAGE: + case NDMP4_LOG_NORMAL: + case NDMP4_LOG_DEBUG: + case NDMP4_LOG_ERROR: + case NDMP4_LOG_WARNING: { + ndmp4_log_message_post *post = + &nmb->body.ndmp4_log_message_post_body; + g_debug("%s", post->entry); + break; + } + + default: + self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR; + self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR; + return FALSE; + } + } else { + self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR; + self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR; + return FALSE; + } + + return TRUE; +} + +/* handler for "unexpected" messages. This handles notifications which happen + * to arrive while the connection is reading the socket looking for a reply. */ +static void +ndmconn_unexpected_impl (struct ndmconn *conn, struct ndmp_msg_buf *nmb) +{ + NDMPConnection *self = NDMP_CONNECTION(conn->context); + + if (!ndmconn_handle_notify(self, nmb)) { + g_warning("ignoring unrecognized, unexpected packet"); + } + + ndmconn_free_nmb(NULL, nmb); +} + +gboolean +ndmp_connection_wait_for_notify( + NDMPConnection *self, + ndmp9_data_halt_reason *data_halt_reason, + ndmp9_mover_halt_reason *mover_halt_reason, + ndmp9_mover_pause_reason *mover_pause_reason, + guint64 *mover_pause_seek_position) +{ + struct ndmp_msg_buf nmb; + + g_assert(!self->startup_err); + + /* initialize output parameters */ + if (data_halt_reason) + *data_halt_reason = NDMP4_DATA_HALT_NA; + if (mover_halt_reason) + *mover_halt_reason = NDMP4_MOVER_HALT_NA; + if (mover_pause_reason) + *mover_pause_reason = NDMP4_MOVER_PAUSE_NA; + if (mover_pause_seek_position) + *mover_pause_seek_position = 0; + + while (1) { + gboolean found = FALSE; + + /* if any desired notifications have been received, then we're + * done */ + if (data_halt_reason && self->data_halt_reason) { + found = TRUE; + *data_halt_reason = self->data_halt_reason; + self->data_halt_reason = NDMP4_DATA_HALT_NA; + } + + if (mover_halt_reason && self->mover_halt_reason) { + found = TRUE; + *mover_halt_reason = self->mover_halt_reason; + self->mover_halt_reason = NDMP4_MOVER_HALT_NA; + } + + if (mover_pause_reason && self->mover_pause_reason) { + found = TRUE; + *mover_pause_reason = self->mover_pause_reason; + if (mover_pause_seek_position) + *mover_pause_seek_position = self->mover_pause_seek_position; + self->mover_pause_reason = NDMP4_MOVER_PAUSE_NA; + self->mover_pause_seek_position = 0; + } + + if (found) + return TRUE; + + /* otherwise, wait for an incoming packet and handle it, then try + * again */ + g_static_mutex_lock(&ndmlib_mutex); + NDMOS_MACRO_ZEROFILL(&nmb); + nmb.protocol_version = NDMP4VER; + self->last_rc = ndmconn_recv_nmb(self->conn, &nmb); + g_static_mutex_unlock(&ndmlib_mutex); + + if (self->last_rc) { + /* (nothing to free) */ + return FALSE; + } + + ndmconn_handle_notify(self, &nmb); + } +} + +/* + * Class Mechanics + */ + +static void +ndmp_connection_class_init( + NDMPConnectionClass * c) +{ + GObjectClass *goc = (GObjectClass *)c; + + goc->finalize = finalize_impl; + + parent_class = g_type_class_peek_parent(c); +} + +GType +ndmp_connection_get_type(void) +{ + static GType type = 0; + if G_UNLIKELY(type == 0) { + static const GTypeInfo info = { + sizeof (NDMPConnectionClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) ndmp_connection_class_init, + (GClassFinalizeFunc) NULL, + NULL /* class_data */, + sizeof (NDMPConnection), + 0 /* n_preallocs */, + (GInstanceInitFunc) NULL, + NULL + }; + + type = g_type_register_static (G_TYPE_OBJECT, "NDMPConnection", &info, + (GTypeFlags)0); + } + return type; +} + +/* Method stubs */ + +/* + * Constructor + */ + +NDMPConnection * +ndmp_connection_new( + gchar *hostname, + gint port, + gchar *username, + gchar *password, + gchar *auth) +{ + NDMPConnection *self = NULL; + gchar *key = NULL; + gchar *errmsg = NULL; + struct ndmconn *conn = NULL; + int rc; + static int next_connid = 1; + static GStaticMutex next_connid_mutex = G_STATIC_MUTEX_INIT; + + conn = ndmconn_initialize(NULL, "amanda-server"); + if (!conn) { + errmsg = "could not initialize ndmconn"; + goto out; + } + + /* set up a handler for unexpected messages, which should generally + * be notifications */ + conn->unexpected = ndmconn_unexpected_impl; + + if (ndmconn_connect_host_port(conn, hostname, port, 0) != 0) { + errmsg = ndmconn_get_err_msg(conn); + ndmconn_destruct(conn); + goto out; + } + + if (0 == g_ascii_strcasecmp(auth, "void")) { + rc = 0; /* don't authenticate */ + } else if (0 == g_ascii_strcasecmp(auth, "none")) { + rc = ndmconn_auth_none(conn); + } else if (0 == g_ascii_strcasecmp(auth, "md5")) { + rc = ndmconn_auth_md5(conn, username, password); + } else if (0 == g_ascii_strcasecmp(auth, "text")) { + rc = ndmconn_auth_text(conn, username, password); + } else { + errmsg = "invalid auth type"; + goto out; + } + + if (rc != 0) { + errmsg = ndmconn_get_err_msg(conn); + ndmconn_destruct(conn); + goto out; + } + + if (conn->protocol_version != NDMP4VER) { + errmsg = g_strdup_printf("Only NDMPv4 is supported; got NDMPv%d", + conn->protocol_version); + ndmconn_destruct(conn); + goto out; + } + + self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL)); + self->conn = conn; + g_static_mutex_lock(&next_connid_mutex); + self->connid = next_connid++; + g_static_mutex_unlock(&next_connid_mutex); + conn->context = (void *)self; + g_debug("opening new NDMPConnection #%d: to %s:%d", self->connid, hostname, port); + +out: + /* make a "fake" error connection if we have an error message. Note that + * this object is not added to the instances hash */ + if (errmsg) { + self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL)); + self->startup_err = errmsg; + errmsg = NULL; + } + + return self; +}