2 * Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * Contact information: Zmanda Inc., 465 N Mathlida Ave, Suite 300
18 * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
22 #include "ndmpconnobj.h"
25 * NDMPConnection class implementation
28 /* level at which to snoop when VERBOSE is set; 8 = everything but hexdumps,
29 * and 5 = packets without details */
32 static GObjectClass *parent_class = NULL;
34 /* and equipment to ensure we only talk to ndmlib in one thread at a time, even
35 * using multiple connections. The ndmlib code is not necessarily reentrant,
36 * so this is better safe than sorry. */
37 static GStaticMutex ndmlib_mutex = G_STATIC_MUTEX_INIT;
39 /* macros like those in ndmlib.h, but designed for use in this class */
40 /* (copied from ndmp-src/ndmlib.h; see that file for copyright and license) */
42 #define NDMP_TRANS(SELF, TYPE) \
44 struct ndmp_xa_buf * xa = &(SELF)->conn->call_xa_buf; \
45 TYPE##_request * request; \
46 TYPE##_reply * reply; \
47 request = &xa->request.body.TYPE##_request_body; \
48 reply = &xa->reply.body.TYPE##_reply_body; \
49 NDMOS_MACRO_ZEROFILL (xa); \
50 xa->request.protocol_version = NDMP4VER; \
51 xa->request.header.message = (ndmp0_message) MT_##TYPE; \
52 g_static_mutex_lock(&ndmlib_mutex); \
55 #define NDMP_TRANS_NO_REQUEST(SELF, TYPE) \
57 struct ndmp_xa_buf * xa = &(SELF)->conn->call_xa_buf; \
58 TYPE##_reply * reply; \
59 reply = &xa->reply.body.TYPE##_reply_body; \
60 NDMOS_MACRO_ZEROFILL (xa); \
61 xa->request.protocol_version = NDMP4VER; \
62 xa->request.header.message = (ndmp0_message) MT_##TYPE; \
63 g_static_mutex_lock(&ndmlib_mutex); \
66 #define NDMP_CALL(SELF) \
68 (SELF)->last_rc = (*(SELF)->conn->call)((SELF)->conn, xa); \
69 if ((SELF)->last_rc) { \
71 g_static_mutex_unlock(&ndmlib_mutex); \
76 #define NDMP_FREE() ndmconn_free_nmb(NULL, &xa->reply)
79 g_static_mutex_unlock(&ndmlib_mutex); \
87 finalize_impl(GObject *goself)
89 NDMPConnection *self = NDMP_CONNECTION(goself);
92 G_OBJECT_CLASS(parent_class)->finalize(goself);
94 g_debug("closing conn#%d", self->connid);
96 /* close this connection if necessary */
98 ndmconn_destruct(self->conn);
102 if (self->log_state) {
103 g_free(self->log_state);
104 self->log_state = NULL;
113 ndmp_connection_err_code(
114 NDMPConnection *self)
116 if (self->startup_err) {
118 } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) {
119 return self->conn->last_reply_error;
126 ndmp_connection_err_msg(
127 NDMPConnection *self)
129 if (self->startup_err) {
130 return g_strdup(self->startup_err);
131 } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) {
132 return g_strdup_printf("Error from NDMP server: %s",
133 ndmp9_error_to_str(self->conn->last_reply_error));
134 } else if (self->last_rc) {
135 return g_strdup_printf("ndmconn error %d: %s",
136 self->last_rc, ndmconn_get_err_msg(self->conn));
138 return g_strdup_printf("No error");
143 ndmp_connection_ndmlog_deliver(
146 int lev G_GNUC_UNUSED,
149 NDMPConnection *self = NDMP_CONNECTION(log->cookie);
150 g_debug("conn#%d: %s: %s", self->connid, tag, msg);
154 ndmp_connection_set_verbose(
155 NDMPConnection *self,
158 struct ndmlog *device_ndmlog;
159 g_assert(!self->startup_err);
161 device_ndmlog = g_new0(struct ndmlog, 1);
163 self->log_state = (gpointer)device_ndmlog;
164 device_ndmlog->deliver = ndmp_connection_ndmlog_deliver;
165 device_ndmlog->cookie = self;
168 ndmconn_set_snoop(self->conn,
172 ndmconn_clear_snoop(self->conn);
181 ndmp_connection_scsi_open(
182 NDMPConnection *self,
185 g_assert(!self->startup_err);
187 NDMP_TRANS(self, ndmp4_scsi_open)
188 request->device = device;
196 ndmp_connection_scsi_close(
197 NDMPConnection *self)
199 g_assert(!self->startup_err);
201 NDMP_TRANS_NO_REQUEST(self, ndmp4_scsi_close)
209 ndmp_connection_scsi_execute_cdb(
210 NDMPConnection *self,
211 guint32 flags, /* NDMP4_SCSI_DATA_{IN,OUT}; OUT = to device */
212 guint32 timeout, /* in ms */
217 gsize *actual_dataout_len, /* output */
218 gpointer datain, /* output */
219 gsize datain_max_len, /* output buffer size */
220 gsize *actual_datain_len, /* output */
221 guint8 *status, /* output */
222 gpointer ext_sense, /* output */
223 gsize ext_sense_max_len, /* output buffer size */
224 gsize *actual_ext_sense_len /* output */
227 g_assert(!self->startup_err);
231 if (actual_dataout_len)
232 *actual_dataout_len = 0;
233 if (actual_datain_len)
234 *actual_datain_len = 0;
235 if (actual_ext_sense_len)
236 *actual_ext_sense_len = 0;
238 NDMP_TRANS(self, ndmp4_scsi_execute_cdb)
239 request->flags = flags;
240 request->timeout = timeout;
241 request->datain_len = datain_max_len;
242 request->cdb.cdb_len = cdb_len;
243 request->cdb.cdb_val = cdb;
244 request->dataout.dataout_len = dataout_len;
245 request->dataout.dataout_val = dataout;
250 *status = reply->status;
251 if (actual_dataout_len)
252 *actual_dataout_len = reply->dataout_len;
254 reply->datain.datain_len = MIN(datain_max_len, reply->datain.datain_len);
255 if (actual_datain_len)
256 *actual_datain_len = reply->datain.datain_len;
257 if (datain_max_len && datain)
258 g_memmove(datain, reply->datain.datain_val, reply->datain.datain_len);
260 reply->ext_sense.ext_sense_len = MIN(ext_sense_max_len, reply->ext_sense.ext_sense_len);
261 if (actual_ext_sense_len)
262 *actual_ext_sense_len = reply->ext_sense.ext_sense_len;
263 if (ext_sense_max_len && ext_sense)
264 g_memmove(ext_sense, reply->ext_sense.ext_sense_val, reply->ext_sense.ext_sense_len);
272 ndmp_connection_tape_open(
273 NDMPConnection *self,
275 ndmp9_tape_open_mode mode)
277 g_assert(!self->startup_err);
279 NDMP_TRANS(self, ndmp4_tape_open)
280 request->device = device;
281 request->mode = mode;
289 ndmp_connection_tape_close(
290 NDMPConnection *self)
292 g_assert(!self->startup_err);
294 NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_close)
302 ndmp_connection_tape_mtio(
303 NDMPConnection *self,
304 ndmp9_tape_mtio_op tape_op,
308 g_assert(!self->startup_err);
310 NDMP_TRANS(self, ndmp4_tape_mtio)
311 request->tape_op = tape_op;
312 request->count = count;
314 *resid_count = reply->resid_count;
321 ndmp_connection_tape_write(
322 NDMPConnection *self,
327 g_assert(!self->startup_err);
331 NDMP_TRANS(self, ndmp4_tape_write)
332 request->data_out.data_out_val = buf;
333 request->data_out.data_out_len = len;
335 *count = reply->count;
342 ndmp_connection_tape_read(
343 NDMPConnection *self,
348 g_assert(!self->startup_err);
352 NDMP_TRANS(self, ndmp4_tape_read)
353 request->count = count;
355 *out_count = reply->data_in.data_in_len;
356 g_memmove(buf, reply->data_in.data_in_val, *out_count);
363 ndmp_connection_tape_get_state(
364 NDMPConnection *self,
369 g_assert(!self->startup_err);
371 NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_get_state)
374 if (reply->unsupported & NDMP4_TAPE_STATE_BLOCK_SIZE_UNS)
377 *blocksize = reply->block_size;
379 if (reply->unsupported & NDMP4_TAPE_STATE_FILE_NUM_UNS)
380 *file_num = G_MAXUINT64;
382 *file_num = reply->file_num;
384 if (reply->unsupported & NDMP4_TAPE_STATE_BLOCKNO_UNS)
385 *blockno = G_MAXUINT64;
387 *blockno = reply->blockno;
395 ndmp_connection_mover_set_record_size(
396 NDMPConnection *self,
399 g_assert(!self->startup_err);
401 NDMP_TRANS(self, ndmp4_mover_set_record_size)
402 /* this field is "len" in ndmp4, but "record_size" in ndmp9 */
403 request->len = record_size;
411 ndmp_connection_mover_set_window(
412 NDMPConnection *self,
416 g_assert(!self->startup_err);
418 NDMP_TRANS(self, ndmp4_mover_set_window)
419 request->offset = offset;
420 request->length = length;
428 ndmp_connection_mover_read(
429 NDMPConnection *self,
433 g_assert(!self->startup_err);
435 NDMP_TRANS(self, ndmp4_mover_read)
436 request->offset = offset;
437 request->length = length;
445 ndmp_connection_mover_continue(
446 NDMPConnection *self)
448 g_assert(!self->startup_err);
450 NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_continue)
458 ndmp_connection_mover_listen(
459 NDMPConnection *self,
460 ndmp9_mover_mode mode,
461 ndmp9_addr_type addr_type,
462 DirectTCPAddr **addrs)
464 unsigned int naddrs, i;
467 g_assert(!self->startup_err);
469 NDMP_TRANS(self, ndmp4_mover_listen)
470 request->mode = mode;
471 request->addr_type = addr_type;
474 if (request->addr_type != reply->connect_addr.addr_type) {
475 g_warning("MOVER_LISTEN addr_type mismatch; got %d", reply->connect_addr.addr_type);
478 if (reply->connect_addr.addr_type == NDMP4_ADDR_TCP) {
479 naddrs = reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_len;
480 *addrs = g_new0(DirectTCPAddr, naddrs+1);
481 for (i = 0; i < naddrs; i++) {
482 ndmp4_tcp_addr *na = &reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_val[i];
483 (*addrs)[i].ipv4 = na->ip_addr;
484 (*addrs)[i].port = na->port;
492 ndmp_connection_mover_connect(
493 NDMPConnection *self,
494 ndmp9_mover_mode mode,
495 DirectTCPAddr *addrs)
497 unsigned int naddrs, i;
500 g_assert(!self->startup_err);
504 for (naddrs = 0; addrs[naddrs].ipv4; naddrs++) ;
506 /* convert addrs to an ndmp4_tcp_addr */
507 na = g_new0(ndmp4_tcp_addr, naddrs);
508 for (i = 0; i < naddrs; i++) {
509 na[i].ip_addr = addrs[i].ipv4;
510 na[i].port = addrs[i].port;
514 NDMP_TRANS(self, ndmp4_mover_connect)
515 request->mode = mode;
516 request->addr.addr_type = NDMP4_ADDR_TCP;
517 request->addr.ndmp4_addr_u.tcp_addr.tcp_addr_len = naddrs;
518 request->addr.ndmp4_addr_u.tcp_addr.tcp_addr_val = na;
526 ndmp_connection_mover_abort(
527 NDMPConnection *self)
529 g_assert(!self->startup_err);
531 NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_abort)
539 ndmp_connection_mover_stop(
540 NDMPConnection *self)
542 g_assert(!self->startup_err);
544 NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_stop)
552 ndmp_connection_mover_close(
553 NDMPConnection *self)
555 g_assert(!self->startup_err);
557 NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_close)
564 gboolean ndmp_connection_mover_get_state(
565 NDMPConnection *self,
566 ndmp9_mover_state *state,
567 guint64 *bytes_moved,
568 guint64 *window_offset,
569 guint64 *window_length)
571 g_assert(!self->startup_err);
573 NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_get_state)
575 if (state) *state = reply->state;
576 if (bytes_moved) *bytes_moved = reply->bytes_moved;
577 if (window_offset) *window_offset = reply->window_offset;
578 if (window_length) *window_length = reply->window_length;
585 ndmconn_handle_notify(
586 NDMPConnection *self,
587 struct ndmp_msg_buf *nmb)
589 g_assert(!self->startup_err);
591 if (nmb->header.message_type == NDMP0_MESSAGE_REQUEST) {
592 switch (nmb->header.message) {
593 case NDMP4_NOTIFY_DATA_HALTED: {
594 ndmp4_notify_data_halted_post *post =
595 &nmb->body.ndmp4_notify_data_halted_post_body;
596 self->data_halt_reason = post->reason;
600 case NDMP4_NOTIFY_MOVER_HALTED: {
601 ndmp4_notify_mover_halted_post *post =
602 &nmb->body.ndmp4_notify_mover_halted_post_body;
603 self->mover_halt_reason = post->reason;
607 case NDMP4_NOTIFY_MOVER_PAUSED: {
608 ndmp4_notify_mover_paused_post *post =
609 &nmb->body.ndmp4_notify_mover_paused_post_body;
610 self->mover_pause_reason = post->reason;
611 self->mover_pause_seek_position = post->seek_position;
616 case NDMP4_LOG_MESSAGE:
617 case NDMP4_LOG_NORMAL:
618 case NDMP4_LOG_DEBUG:
619 case NDMP4_LOG_ERROR:
620 case NDMP4_LOG_WARNING: {
621 ndmp4_log_message_post *post =
622 &nmb->body.ndmp4_log_message_post_body;
623 g_debug("%s", post->entry);
628 self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR;
629 self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR;
633 self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR;
634 self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR;
641 /* handler for "unexpected" messages. This handles notifications which happen
642 * to arrive while the connection is reading the socket looking for a reply. */
644 ndmconn_unexpected_impl (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
646 NDMPConnection *self = NDMP_CONNECTION(conn->context);
648 if (!ndmconn_handle_notify(self, nmb)) {
649 g_warning("ignoring unrecognized, unexpected packet");
652 ndmconn_free_nmb(NULL, nmb);
656 ndmp_connection_wait_for_notify(
657 NDMPConnection *self,
658 ndmp9_data_halt_reason *data_halt_reason,
659 ndmp9_mover_halt_reason *mover_halt_reason,
660 ndmp9_mover_pause_reason *mover_pause_reason,
661 guint64 *mover_pause_seek_position)
663 struct ndmp_msg_buf nmb;
665 g_assert(!self->startup_err);
667 /* initialize output parameters */
668 if (data_halt_reason)
669 *data_halt_reason = NDMP4_DATA_HALT_NA;
670 if (mover_halt_reason)
671 *mover_halt_reason = NDMP4_MOVER_HALT_NA;
672 if (mover_pause_reason)
673 *mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
674 if (mover_pause_seek_position)
675 *mover_pause_seek_position = 0;
678 gboolean found = FALSE;
680 SELECT_ARG_TYPE readset;
683 /* if any desired notifications have been received, then we're
685 if (data_halt_reason && self->data_halt_reason) {
687 *data_halt_reason = self->data_halt_reason;
688 self->data_halt_reason = NDMP4_DATA_HALT_NA;
691 if (mover_halt_reason && self->mover_halt_reason) {
693 *mover_halt_reason = self->mover_halt_reason;
694 self->mover_halt_reason = NDMP4_MOVER_HALT_NA;
697 if (mover_pause_reason && self->mover_pause_reason) {
699 *mover_pause_reason = self->mover_pause_reason;
700 if (mover_pause_seek_position)
701 *mover_pause_seek_position = self->mover_pause_seek_position;
702 self->mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
703 self->mover_pause_seek_position = 0;
709 /* otherwise, wait for an incoming packet and handle it, then try
710 * again. There's some select trickery here to avoid hogging the
711 * ndmlib_mutex - basically, we want to block as long as possible
712 * outside of the ndmlib_mutex critical section. This will also be
713 * useful to allow the wait to be aborted. */
714 fd = self->conn->chan.fd;
716 FD_SET(fd, &readset);
717 nfound = select(fd+1, &readset, NULL, NULL, NULL);
719 /* fall on through, blind to any errors - presumably the same error
720 * condition will be caught by ndmconn_recv_nmb. */
722 g_static_mutex_lock(&ndmlib_mutex);
723 NDMOS_MACRO_ZEROFILL(&nmb);
724 nmb.protocol_version = NDMP4VER;
725 self->last_rc = ndmconn_recv_nmb(self->conn, &nmb);
726 g_static_mutex_unlock(&ndmlib_mutex);
729 /* (nothing to free) */
733 ndmconn_handle_notify(self, &nmb);
742 ndmp_connection_class_init(
743 NDMPConnectionClass * c)
745 GObjectClass *goc = (GObjectClass *)c;
747 goc->finalize = finalize_impl;
749 parent_class = g_type_class_peek_parent(c);
753 ndmp_connection_get_type(void)
755 static GType type = 0;
756 if G_UNLIKELY(type == 0) {
757 static const GTypeInfo info = {
758 sizeof (NDMPConnectionClass),
759 (GBaseInitFunc) NULL,
760 (GBaseFinalizeFunc) NULL,
761 (GClassInitFunc) ndmp_connection_class_init,
762 (GClassFinalizeFunc) NULL,
763 NULL /* class_data */,
764 sizeof (NDMPConnection),
766 (GInstanceInitFunc) NULL,
770 type = g_type_register_static (G_TYPE_OBJECT, "NDMPConnection", &info,
790 NDMPConnection *self = NULL;
792 gchar *errmsg = NULL;
793 struct ndmconn *conn = NULL;
795 static int next_connid = 1;
796 static GStaticMutex next_connid_mutex = G_STATIC_MUTEX_INIT;
798 conn = ndmconn_initialize(NULL, "amanda-server");
800 errmsg = "could not initialize ndmconn";
804 /* set up a handler for unexpected messages, which should generally
805 * be notifications */
806 conn->unexpected = ndmconn_unexpected_impl;
808 if (ndmconn_connect_host_port(conn, hostname, port, 0) != 0) {
809 errmsg = ndmconn_get_err_msg(conn);
810 ndmconn_destruct(conn);
814 if (0 == g_ascii_strcasecmp(auth, "void")) {
815 rc = 0; /* don't authenticate */
816 } else if (0 == g_ascii_strcasecmp(auth, "none")) {
817 rc = ndmconn_auth_none(conn);
818 } else if (0 == g_ascii_strcasecmp(auth, "md5")) {
819 rc = ndmconn_auth_md5(conn, username, password);
820 } else if (0 == g_ascii_strcasecmp(auth, "text")) {
821 rc = ndmconn_auth_text(conn, username, password);
823 errmsg = "invalid auth type";
828 errmsg = ndmconn_get_err_msg(conn);
829 ndmconn_destruct(conn);
833 if (conn->protocol_version != NDMP4VER) {
834 errmsg = g_strdup_printf("Only NDMPv4 is supported; got NDMPv%d",
835 conn->protocol_version);
836 ndmconn_destruct(conn);
840 self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL));
842 g_static_mutex_lock(&next_connid_mutex);
843 self->connid = next_connid++;
844 g_static_mutex_unlock(&next_connid_mutex);
845 conn->context = (void *)self;
846 g_debug("opening new NDMPConnection #%d: to %s:%d", self->connid, hostname, port);
849 /* make a "fake" error connection if we have an error message. Note that
850 * this object is not added to the instances hash */
852 self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL));
853 self->startup_err = errmsg;