add bug closure to changelog
[debian/amanda] / ndmp-src / ndmpconnobj.c
1 /*
2  * Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
3  *
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.
7  *
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
11  * for more details.
12  *
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
16  *
17  * Contact information: Zmanda Inc., 465 N Mathlida Ave, Suite 300
18  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19  */
20
21 #include "amanda.h"
22 #include "event.h"
23 #include "sockaddr-util.h"
24 #include "ndmpconnobj.h"
25
26 /*
27  * NDMPConnection class implementation
28  */
29
30 /* level at which to snoop when VERBOSE is set; 8 = everything but hexdumps,
31  * and 5 = packets without details */
32 #define SNOOP_LEVEL 7
33
34 static GObjectClass *parent_class = NULL;
35
36 /* and equipment to ensure we only talk to ndmlib in one thread at a time, even
37  * using multiple connections.  The ndmlib code is not necessarily reentrant,
38  * so this is better safe than sorry. */
39 static GStaticMutex ndmlib_mutex = G_STATIC_MUTEX_INIT;
40
41 /* macros like those in ndmlib.h, but designed for use in this class */
42 /* (copied from ndmp-src/ndmlib.h; see that file for copyright and license) */
43
44 #define NDMP_TRANS(SELF, TYPE) \
45   { \
46         struct ndmp_xa_buf *    xa = &(SELF)->conn->call_xa_buf; \
47         TYPE##_request * request; \
48         TYPE##_reply   * reply; \
49         request = &xa->request.body.TYPE##_request_body; \
50         reply = &xa->reply.body.TYPE##_reply_body; \
51         NDMOS_MACRO_ZEROFILL (xa); \
52         xa->request.protocol_version = NDMP4VER; \
53         xa->request.header.message = (ndmp0_message) MT_##TYPE; \
54         g_static_mutex_lock(&ndmlib_mutex); \
55      {
56
57 #define NDMP_TRANS_NO_REQUEST(SELF, TYPE) \
58   { \
59         struct ndmp_xa_buf *    xa = &(SELF)->conn->call_xa_buf; \
60         TYPE##_reply   * reply; \
61         reply = &xa->reply.body.TYPE##_reply_body; \
62         NDMOS_MACRO_ZEROFILL (xa); \
63         xa->request.protocol_version = NDMP4VER; \
64         xa->request.header.message = (ndmp0_message) MT_##TYPE; \
65         g_static_mutex_lock(&ndmlib_mutex); \
66      {
67
68 #define NDMP_CALL(SELF) \
69     do { \
70         (SELF)->last_rc = (*(SELF)->conn->call)((SELF)->conn, xa); \
71         if ((SELF)->last_rc) { \
72             NDMP_FREE(); \
73             g_static_mutex_unlock(&ndmlib_mutex); \
74             return FALSE; \
75         } \
76     } while (0);
77
78 #define NDMP_FREE() ndmconn_free_nmb(NULL, &xa->reply)
79
80 #define NDMP_END \
81         g_static_mutex_unlock(&ndmlib_mutex); \
82     } }
83
84 /*
85  * Methods
86  */
87
88 static void
89 finalize_impl(GObject *goself)
90 {
91     NDMPConnection *self = NDMP_CONNECTION(goself);
92
93     /* chain up first */
94     G_OBJECT_CLASS(parent_class)->finalize(goself);
95
96     g_debug("closing conn#%d", self->connid);
97
98     /* close this connection if necessary */
99     if (self->conn) {
100         ndmconn_destruct(self->conn);
101         self->conn = NULL;
102     }
103
104     if (self->log_state) {
105         g_free(self->log_state);
106         self->log_state = NULL;
107     }
108 }
109
110 /*
111  * Error handling
112  */
113
114 ndmp4_error
115 ndmp_connection_err_code(
116     NDMPConnection *self)
117 {
118     if (self->startup_err) {
119         return NDMP4_IO_ERR;
120     } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) {
121         return self->conn->last_reply_error;
122     } else {
123         return NDMP4_NO_ERR;
124     }
125 }
126
127 gchar *
128 ndmp_connection_err_msg(
129     NDMPConnection *self)
130 {
131     if (self->startup_err) {
132         return g_strdup(self->startup_err);
133     } else if (self->last_rc == NDMCONN_CALL_STATUS_REPLY_ERROR) {
134         return g_strdup_printf("Error from NDMP server: %s",
135                     ndmp9_error_to_str(self->conn->last_reply_error));
136     } else if (self->last_rc) {
137         return g_strdup_printf("ndmconn error %d: %s",
138                     self->last_rc, ndmconn_get_err_msg(self->conn));
139     } else {
140         return g_strdup_printf("No error");
141     }
142 }
143
144 static void
145 ndmp_connection_ndmlog_deliver(
146     struct ndmlog *log,
147     char *tag,
148     int lev G_GNUC_UNUSED,
149     char *msg)
150 {
151     NDMPConnection *self = NDMP_CONNECTION(log->cookie);
152     g_debug("conn#%d: %s: %s", self->connid, tag, msg);
153 }
154
155 void
156 ndmp_connection_set_verbose(
157     NDMPConnection *self,
158     gboolean verbose)
159 {
160     struct ndmlog *device_ndmlog;
161     g_assert(!self->startup_err);
162
163     device_ndmlog = g_new0(struct ndmlog, 1);
164
165     self->log_state = (gpointer)device_ndmlog;
166     device_ndmlog->deliver = ndmp_connection_ndmlog_deliver;
167     device_ndmlog->cookie = self;
168
169     if (verbose) {
170         ndmconn_set_snoop(self->conn,
171             device_ndmlog,
172             SNOOP_LEVEL);
173     } else {
174         ndmconn_clear_snoop(self->conn);
175     }
176 }
177
178 /*
179  * Operations
180  */
181
182 gboolean
183 ndmp_connection_scsi_open(
184         NDMPConnection *self,
185         gchar *device)
186 {
187     g_assert(!self->startup_err);
188
189     NDMP_TRANS(self, ndmp4_scsi_open)
190         request->device = device;
191         NDMP_CALL(self);
192         NDMP_FREE();
193     NDMP_END
194     return TRUE;
195 }
196
197 gboolean
198 ndmp_connection_scsi_close(
199         NDMPConnection *self)
200 {
201     g_assert(!self->startup_err);
202
203     NDMP_TRANS_NO_REQUEST(self, ndmp4_scsi_close)
204         NDMP_CALL(self);
205         NDMP_FREE();
206     NDMP_END
207     return TRUE;
208 }
209
210 gboolean
211 ndmp_connection_scsi_execute_cdb(
212         NDMPConnection *self,
213         guint32 flags, /* NDMP4_SCSI_DATA_{IN,OUT}; OUT = to device */
214         guint32 timeout, /* in ms */
215         gpointer cdb,
216         gsize cdb_len,
217         gpointer dataout,
218         gsize dataout_len,
219         gsize *actual_dataout_len, /* output */
220         gpointer datain, /* output */
221         gsize datain_max_len, /* output buffer size */
222         gsize *actual_datain_len, /* output */
223         guint8 *status, /* output */
224         gpointer ext_sense, /* output */
225         gsize ext_sense_max_len, /* output buffer size */
226         gsize *actual_ext_sense_len /* output */
227         )
228 {
229     g_assert(!self->startup_err);
230
231     if (status)
232         *status = 0;
233     if (actual_dataout_len)
234         *actual_dataout_len = 0;
235     if (actual_datain_len)
236         *actual_datain_len = 0;
237     if (actual_ext_sense_len)
238         *actual_ext_sense_len = 0;
239
240     NDMP_TRANS(self, ndmp4_scsi_execute_cdb)
241         request->flags = flags;
242         request->timeout = timeout;
243         request->datain_len = datain_max_len;
244         request->cdb.cdb_len = cdb_len;
245         request->cdb.cdb_val = cdb;
246         request->dataout.dataout_len = dataout_len;
247         request->dataout.dataout_val = dataout;
248
249         NDMP_CALL(self);
250
251         if (status)
252             *status = reply->status;
253         if (actual_dataout_len)
254             *actual_dataout_len = reply->dataout_len;
255
256         reply->datain.datain_len = MIN(datain_max_len, reply->datain.datain_len);
257         if (actual_datain_len)
258             *actual_datain_len = reply->datain.datain_len;
259         if (datain_max_len && datain)
260             g_memmove(datain, reply->datain.datain_val, reply->datain.datain_len);
261
262         reply->ext_sense.ext_sense_len = MIN(ext_sense_max_len, reply->ext_sense.ext_sense_len);
263         if (actual_ext_sense_len)
264             *actual_ext_sense_len = reply->ext_sense.ext_sense_len;
265         if (ext_sense_max_len && ext_sense)
266             g_memmove(ext_sense, reply->ext_sense.ext_sense_val, reply->ext_sense.ext_sense_len);
267
268         NDMP_FREE();
269     NDMP_END
270     return TRUE;
271 }
272
273 gboolean
274 ndmp_connection_tape_open(
275         NDMPConnection *self,
276         gchar *device,
277         ndmp9_tape_open_mode mode)
278 {
279     g_assert(!self->startup_err);
280
281     NDMP_TRANS(self, ndmp4_tape_open)
282         request->device = device;
283         request->mode = mode;
284         NDMP_CALL(self);
285         NDMP_FREE();
286     NDMP_END
287     return TRUE;
288 }
289
290 gboolean
291 ndmp_connection_tape_close(
292         NDMPConnection *self)
293 {
294     g_assert(!self->startup_err);
295
296     NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_close)
297         NDMP_CALL(self);
298         NDMP_FREE();
299     NDMP_END
300     return TRUE;
301 }
302
303 gboolean
304 ndmp_connection_tape_mtio(
305         NDMPConnection *self,
306         ndmp9_tape_mtio_op tape_op,
307         gint count,
308         guint *resid_count)
309 {
310     g_assert(!self->startup_err);
311
312     NDMP_TRANS(self, ndmp4_tape_mtio)
313         request->tape_op = tape_op;
314         request->count = count;
315         NDMP_CALL(self);
316         *resid_count = reply->resid_count;
317         NDMP_FREE();
318     NDMP_END
319     return TRUE;
320 }
321
322 gboolean
323 ndmp_connection_tape_write(
324         NDMPConnection *self,
325         gpointer buf,
326         guint64 len,
327         guint64 *count)
328 {
329     g_assert(!self->startup_err);
330
331     *count = 0;
332
333     NDMP_TRANS(self, ndmp4_tape_write)
334         request->data_out.data_out_val = buf;
335         request->data_out.data_out_len = len;
336         NDMP_CALL(self);
337         *count = reply->count;
338         NDMP_FREE();
339     NDMP_END
340     return TRUE;
341 }
342
343 gboolean
344 ndmp_connection_tape_read(
345         NDMPConnection *self,
346         gpointer buf,
347         guint64 count,
348         guint64 *out_count)
349 {
350     g_assert(!self->startup_err);
351
352     *out_count = 0;
353
354     NDMP_TRANS(self, ndmp4_tape_read)
355         request->count = count;
356         NDMP_CALL(self);
357         *out_count = reply->data_in.data_in_len;
358         g_memmove(buf, reply->data_in.data_in_val, *out_count);
359         NDMP_FREE();
360     NDMP_END
361     return TRUE;
362 }
363
364 gboolean
365 ndmp_connection_tape_get_state(
366         NDMPConnection *self,
367         guint64 *blocksize,
368         guint64 *file_num,
369         guint64 *blockno)
370 {
371     g_assert(!self->startup_err);
372
373     NDMP_TRANS_NO_REQUEST(self, ndmp4_tape_get_state)
374         NDMP_CALL(self);
375
376         if (reply->unsupported & NDMP4_TAPE_STATE_BLOCK_SIZE_UNS)
377             *blocksize = 0;
378         else
379             *blocksize = reply->block_size;
380
381         if (reply->unsupported & NDMP4_TAPE_STATE_FILE_NUM_UNS)
382             *file_num = G_MAXUINT64;
383         else
384             *file_num = reply->file_num;
385
386         if (reply->unsupported & NDMP4_TAPE_STATE_BLOCKNO_UNS)
387             *blockno = G_MAXUINT64;
388         else
389             *blockno = reply->blockno;
390
391         NDMP_FREE();
392     NDMP_END
393     return TRUE;
394 }
395
396 gboolean
397 ndmp_connection_mover_set_record_size(
398         NDMPConnection *self,
399         guint32 record_size)
400 {
401     g_assert(!self->startup_err);
402
403     NDMP_TRANS(self, ndmp4_mover_set_record_size)
404         /* this field is "len" in ndmp4, but "record_size" in ndmp9 */
405         request->len = record_size;
406         NDMP_CALL(self);
407         NDMP_FREE();
408     NDMP_END
409     return TRUE;
410 }
411
412 gboolean
413 ndmp_connection_mover_set_window(
414         NDMPConnection *self,
415         guint64 offset,
416         guint64 length)
417 {
418     g_assert(!self->startup_err);
419
420     NDMP_TRANS(self, ndmp4_mover_set_window)
421         request->offset = offset;
422         request->length = length;
423         NDMP_CALL(self);
424         NDMP_FREE();
425     NDMP_END
426     return TRUE;
427 }
428
429 gboolean
430 ndmp_connection_mover_read(
431         NDMPConnection *self,
432         guint64 offset,
433         guint64 length)
434 {
435     g_assert(!self->startup_err);
436
437     NDMP_TRANS(self, ndmp4_mover_read)
438         request->offset = offset;
439         request->length = length;
440         NDMP_CALL(self);
441         NDMP_FREE();
442     NDMP_END
443     return TRUE;
444 }
445
446 gboolean
447 ndmp_connection_mover_continue(
448         NDMPConnection *self)
449 {
450     g_assert(!self->startup_err);
451
452     NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_continue)
453         NDMP_CALL(self);
454         NDMP_FREE();
455     NDMP_END
456     return TRUE;
457 }
458
459 gboolean
460 ndmp_connection_mover_listen(
461         NDMPConnection *self,
462         ndmp9_mover_mode mode,
463         ndmp9_addr_type addr_type,
464         DirectTCPAddr **addrs)
465 {
466     unsigned int naddrs, i;
467     *addrs = NULL;
468
469     g_assert(!self->startup_err);
470
471     NDMP_TRANS(self, ndmp4_mover_listen)
472         request->mode = mode;
473         request->addr_type = addr_type;
474         NDMP_CALL(self);
475
476         if (request->addr_type != reply->connect_addr.addr_type) {
477             g_warning("MOVER_LISTEN addr_type mismatch; got %d", reply->connect_addr.addr_type);
478         }
479
480         if (reply->connect_addr.addr_type == NDMP4_ADDR_TCP) {
481             naddrs = reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_len;
482             *addrs = g_new0(DirectTCPAddr, naddrs+1);
483             for (i = 0; i < naddrs; i++) {
484                 ndmp4_tcp_addr *na = &reply->connect_addr.ndmp4_addr_u.tcp_addr.tcp_addr_val[i];
485                 (*addrs)[i].sin.sin_family = AF_INET;
486                 (*addrs)[i].sin.sin_addr.s_addr = htonl(na->ip_addr);
487                 SU_SET_PORT(addrs[i], na->port);
488             }
489         }
490         NDMP_FREE();
491     NDMP_END
492     return TRUE;
493 }
494
495 ndmp_connection_mover_connect(
496         NDMPConnection *self,
497         ndmp9_mover_mode mode,
498         DirectTCPAddr *addrs)
499 {
500     unsigned int naddrs, i;
501     ndmp4_tcp_addr *na;
502
503     g_assert(!self->startup_err);
504
505     /* count addrs */
506     g_assert(addrs);
507     for (naddrs = 0; SU_GET_FAMILY(&addrs[naddrs]) != 0; naddrs++) ;
508
509     /* convert addrs to an ndmp4_tcp_addr */
510     na = g_new0(ndmp4_tcp_addr, naddrs);
511     for (i = 0; i < naddrs; i++) {
512         na[i].ip_addr = ntohl(addrs[i].sin.sin_addr.s_addr);
513         na[i].port = SU_GET_PORT(&addrs[i]);
514     }
515
516
517     NDMP_TRANS(self, ndmp4_mover_connect)
518         request->mode = mode;
519         request->addr.addr_type = NDMP4_ADDR_TCP;
520         request->addr.ndmp4_addr_u.tcp_addr.tcp_addr_len = naddrs;
521         request->addr.ndmp4_addr_u.tcp_addr.tcp_addr_val = na;
522         NDMP_CALL(self);
523         NDMP_FREE();
524     NDMP_END
525     return TRUE;
526 }
527
528 gboolean
529 ndmp_connection_mover_abort(
530         NDMPConnection *self)
531 {
532     g_assert(!self->startup_err);
533
534     NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_abort)
535         NDMP_CALL(self);
536         NDMP_FREE();
537     NDMP_END
538     return TRUE;
539 }
540
541 gboolean
542 ndmp_connection_mover_stop(
543         NDMPConnection *self)
544 {
545     g_assert(!self->startup_err);
546
547     NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_stop)
548         NDMP_CALL(self);
549         NDMP_FREE();
550     NDMP_END
551     return TRUE;
552 }
553
554 gboolean
555 ndmp_connection_mover_close(
556         NDMPConnection *self)
557 {
558     g_assert(!self->startup_err);
559
560     NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_close)
561         NDMP_CALL(self);
562         NDMP_FREE();
563     NDMP_END
564     return TRUE;
565 }
566
567 gboolean ndmp_connection_mover_get_state(
568         NDMPConnection *self,
569         ndmp9_mover_state *state,
570         guint64 *bytes_moved,
571         guint64 *window_offset,
572         guint64 *window_length)
573 {
574     g_assert(!self->startup_err);
575
576     NDMP_TRANS_NO_REQUEST(self, ndmp4_mover_get_state)
577         NDMP_CALL(self);
578         if (state) *state = reply->state;
579         if (bytes_moved) *bytes_moved = reply->bytes_moved;
580         if (window_offset) *window_offset = reply->window_offset;
581         if (window_length) *window_length = reply->window_length;
582         NDMP_FREE();
583     NDMP_END
584     return TRUE;
585 }
586
587 static gboolean
588 ndmconn_handle_notify(
589     NDMPConnection *self,
590     struct ndmp_msg_buf *nmb)
591 {
592     g_assert(!self->startup_err);
593
594     if (nmb->header.message_type == NDMP0_MESSAGE_REQUEST) {
595         switch (nmb->header.message) {
596             case NDMP4_NOTIFY_DATA_HALTED: {
597                 ndmp4_notify_data_halted_post *post =
598                     &nmb->body.ndmp4_notify_data_halted_post_body;
599                 self->data_halt_reason = post->reason;
600                 break;
601             }
602
603             case NDMP4_NOTIFY_MOVER_HALTED: {
604                 ndmp4_notify_mover_halted_post *post =
605                     &nmb->body.ndmp4_notify_mover_halted_post_body;
606                 self->mover_halt_reason = post->reason;
607                 break;
608             }
609
610             case NDMP4_NOTIFY_MOVER_PAUSED: {
611                 ndmp4_notify_mover_paused_post *post =
612                     &nmb->body.ndmp4_notify_mover_paused_post_body;
613                 self->mover_pause_reason = post->reason;
614                 self->mover_pause_seek_position = post->seek_position;
615                 break;
616             }
617
618             case NDMP4_LOG_FILE:
619             case NDMP4_LOG_MESSAGE:
620             case NDMP4_LOG_NORMAL:
621             case NDMP4_LOG_DEBUG:
622             case NDMP4_LOG_ERROR:
623             case NDMP4_LOG_WARNING: {
624                 ndmp4_log_message_post *post =
625                     &nmb->body.ndmp4_log_message_post_body;
626                 g_debug("%s", post->entry);
627                 break;
628             }
629
630             default:
631                 self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR;
632                 self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR;
633                 return FALSE;
634         }
635     } else {
636         self->last_rc = NDMCONN_CALL_STATUS_REPLY_ERROR;
637         self->conn->last_reply_error = NDMP4_ILLEGAL_STATE_ERR;
638         return FALSE;
639     }
640
641     return TRUE;
642 }
643
644 /* handler for "unexpected" messages.  This handles notifications which happen
645  * to arrive while the connection is reading the socket looking for a reply. */
646 static void
647 ndmconn_unexpected_impl (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
648 {
649     NDMPConnection *self = NDMP_CONNECTION(conn->context);
650
651     if (!ndmconn_handle_notify(self, nmb)) {
652         g_warning("ignoring unrecognized, unexpected packet");
653     }
654
655     ndmconn_free_nmb(NULL, nmb);
656 }
657
658 gboolean
659 ndmp_connection_wait_for_notify(
660         NDMPConnection *self,
661         ndmp9_data_halt_reason *data_halt_reason,
662         ndmp9_mover_halt_reason *mover_halt_reason,
663         ndmp9_mover_pause_reason *mover_pause_reason,
664         guint64 *mover_pause_seek_position)
665 {
666     struct ndmp_msg_buf nmb;
667
668     g_assert(!self->startup_err);
669
670     /* initialize output parameters */
671     if (data_halt_reason)
672         *data_halt_reason = NDMP4_DATA_HALT_NA;
673     if (mover_halt_reason)
674         *mover_halt_reason = NDMP4_MOVER_HALT_NA;
675     if (mover_pause_reason)
676         *mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
677     if (mover_pause_seek_position)
678         *mover_pause_seek_position = 0;
679
680     while (1) {
681         gboolean found = FALSE;
682         int fd;
683         SELECT_ARG_TYPE readset;
684         int nfound;
685
686         /* if any desired notifications have been received, then we're
687          * done */
688         if (data_halt_reason && self->data_halt_reason) {
689             found = TRUE;
690             *data_halt_reason = self->data_halt_reason;
691             self->data_halt_reason = NDMP4_DATA_HALT_NA;
692         }
693
694         if (mover_halt_reason && self->mover_halt_reason) {
695             found = TRUE;
696             *mover_halt_reason = self->mover_halt_reason;
697             self->mover_halt_reason = NDMP4_MOVER_HALT_NA;
698         }
699
700         if (mover_pause_reason && self->mover_pause_reason) {
701             found = TRUE;
702             *mover_pause_reason = self->mover_pause_reason;
703             if (mover_pause_seek_position)
704                 *mover_pause_seek_position = self->mover_pause_seek_position;
705             self->mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
706             self->mover_pause_seek_position = 0;
707         }
708
709         if (found)
710             return TRUE;
711
712         /* otherwise, wait for an incoming packet and handle it, then try
713          * again.  There's some select trickery here to avoid hogging the
714          * ndmlib_mutex - basically, we want to block as long as possible
715          * outside of the ndmlib_mutex critical section.  This will also be
716          * useful to allow the wait to be aborted. */
717         fd = self->conn->chan.fd;
718         FD_ZERO(&readset);
719         FD_SET(fd, &readset);
720         nfound = select(fd+1, &readset, NULL, NULL, NULL);
721
722         /* fall on through, blind to any errors - presumably the same error
723          * condition will be caught by ndmconn_recv_nmb. */
724
725         g_static_mutex_lock(&ndmlib_mutex);
726         NDMOS_MACRO_ZEROFILL(&nmb);
727         nmb.protocol_version = NDMP4VER;
728         self->last_rc = ndmconn_recv_nmb(self->conn, &nmb);
729         g_static_mutex_unlock(&ndmlib_mutex);
730
731         if (self->last_rc) {
732             /* (nothing to free) */
733             return FALSE;
734         }
735
736         ndmconn_handle_notify(self, &nmb);
737     }
738 }
739
740 typedef struct notify_data {
741     NDMPConnection *self;
742     ndmp9_data_halt_reason *data_halt_reason;
743     ndmp9_mover_halt_reason *mover_halt_reason;
744     ndmp9_mover_pause_reason *mover_pause_reason;
745     guint64 *mover_pause_seek_position;
746     GMutex *abort_mutex;
747     GCond *abort_cond;
748     int status;
749     event_handle_t *read_event;
750 } notify_data;
751
752 static void handle_notify(void *cookie);
753
754 int
755 ndmp_connection_wait_for_notify_with_cond(
756         NDMPConnection *self,
757         ndmp9_data_halt_reason *data_halt_reason,
758         ndmp9_mover_halt_reason *mover_halt_reason,
759         ndmp9_mover_pause_reason *mover_pause_reason,
760         guint64 *mover_pause_seek_position,
761         GMutex *abort_mutex,
762         GCond *abort_cond)
763 {
764     struct ndmp_msg_buf nmb;
765     notify_data ndata;
766     gboolean found = FALSE;
767
768     ndata.self = self;
769     ndata.data_halt_reason= data_halt_reason;
770     ndata.mover_halt_reason= mover_halt_reason;
771     ndata.mover_pause_reason= mover_pause_reason;
772     ndata.mover_pause_seek_position = mover_pause_seek_position;
773     ndata.abort_mutex = abort_mutex;
774     ndata.abort_cond = abort_cond;
775     ndata.status = 2;
776
777     g_assert(!self->startup_err);
778
779     /* initialize output parameters */
780     if (data_halt_reason)
781         *data_halt_reason = NDMP4_DATA_HALT_NA;
782     if (mover_halt_reason)
783         *mover_halt_reason = NDMP4_MOVER_HALT_NA;
784     if (mover_pause_reason)
785         *mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
786     if (mover_pause_seek_position)
787         *mover_pause_seek_position = 0;
788
789     /* if any desired notifications have been received, then we're
790      * done */
791     if (data_halt_reason && self->data_halt_reason) {
792         found = TRUE;
793         *data_halt_reason = self->data_halt_reason;
794         self->data_halt_reason = NDMP4_DATA_HALT_NA;
795     }
796
797     if (mover_halt_reason && self->mover_halt_reason) {
798         found = TRUE;
799         *mover_halt_reason = self->mover_halt_reason;
800         self->mover_halt_reason = NDMP4_MOVER_HALT_NA;
801     }
802
803     if (mover_pause_reason && self->mover_pause_reason) {
804         found = TRUE;
805         *mover_pause_reason = self->mover_pause_reason;
806         if (mover_pause_seek_position)
807             *mover_pause_seek_position = self->mover_pause_seek_position;
808         self->mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
809         self->mover_pause_seek_position = 0;
810     }
811
812     if (found)
813         return TRUE;
814
815         /* otherwise, wait for an incoming packet and handle it, then try
816          * again.  There's some select trickery here to avoid hogging the
817          * ndmlib_mutex - basically, we want to block as long as possible
818          * outside of the ndmlib_mutex critical section.  This will also be
819          * useful to allow the wait to be aborted. */
820
821     ndata.read_event = event_register(self->conn->chan.fd,
822                                       EV_READFD, handle_notify, &ndata);
823
824     g_cond_wait(abort_cond, abort_mutex);
825
826     if (ndata.read_event) {
827         event_release(ndata.read_event);
828     }
829     if (ndata.status == 2) {
830         ndmp_connection_mover_abort(self);
831         ndmp_connection_mover_stop(self);
832     }
833     return ndata.status;
834
835 }
836
837 static void
838 handle_notify(void *cookie)
839 {
840     notify_data *ndata = cookie;
841     struct ndmp_msg_buf nmb;
842     gboolean found = FALSE;
843
844     g_mutex_lock(ndata->abort_mutex);
845
846     event_release(ndata->read_event);
847     ndata->read_event = NULL;
848
849     g_static_mutex_lock(&ndmlib_mutex);
850     NDMOS_MACRO_ZEROFILL(&nmb);
851     nmb.protocol_version = NDMP4VER;
852     ndata->self->last_rc = ndmconn_recv_nmb(ndata->self->conn, &nmb);
853     g_static_mutex_unlock(&ndmlib_mutex);
854
855     if (ndata->self->last_rc) {
856         /* (nothing to free) */
857         ndata->status = 1;
858         goto notify_done;
859     }
860
861     ndmconn_handle_notify(ndata->self, &nmb);
862
863
864     /* if any desired notifications have been received, then we're
865      * done */
866     if (ndata->data_halt_reason && ndata->self->data_halt_reason) {
867         found = TRUE;
868         *ndata->data_halt_reason = ndata->self->data_halt_reason;
869         ndata->self->data_halt_reason = NDMP4_DATA_HALT_NA;
870     }
871
872     if (ndata->mover_halt_reason && ndata->self->mover_halt_reason) {
873         found = TRUE;
874         *ndata->mover_halt_reason = ndata->self->mover_halt_reason;
875         ndata->self->mover_halt_reason = NDMP4_MOVER_HALT_NA;
876     }
877
878     if (ndata->mover_pause_reason && ndata->self->mover_pause_reason) {
879         found = TRUE;
880         *ndata->mover_pause_reason = ndata->self->mover_pause_reason;
881         if (ndata->mover_pause_seek_position)
882             *ndata->mover_pause_seek_position = ndata->self->mover_pause_seek_position;
883         ndata->self->mover_pause_reason = NDMP4_MOVER_PAUSE_NA;
884         ndata->self->mover_pause_seek_position = 0;
885     }
886
887     if (!found) {
888         ndata->read_event = event_register(ndata->self->conn->chan.fd,
889                                            EV_READFD, handle_notify, ndata);
890         g_mutex_unlock(ndata->abort_mutex);
891         return;
892     }
893
894     ndata->status = 0;
895 notify_done:
896     g_cond_broadcast(ndata->abort_cond);
897     g_mutex_unlock(ndata->abort_mutex);
898 }
899 /*
900  * Class Mechanics
901  */
902
903 static void
904 ndmp_connection_class_init(
905         NDMPConnectionClass * c)
906 {
907     GObjectClass *goc = (GObjectClass *)c;
908
909     goc->finalize =  finalize_impl;
910
911     parent_class = g_type_class_peek_parent(c);
912 }
913
914 GType
915 ndmp_connection_get_type(void)
916 {
917     static GType type = 0;
918     if G_UNLIKELY(type == 0) {
919         static const GTypeInfo info = {
920             sizeof (NDMPConnectionClass),
921             (GBaseInitFunc) NULL,
922             (GBaseFinalizeFunc) NULL,
923             (GClassInitFunc) ndmp_connection_class_init,
924             (GClassFinalizeFunc) NULL,
925             NULL /* class_data */,
926             sizeof (NDMPConnection),
927             0 /* n_preallocs */,
928             (GInstanceInitFunc) NULL,
929             NULL
930         };
931
932         type = g_type_register_static (G_TYPE_OBJECT, "NDMPConnection", &info,
933                                        (GTypeFlags)0);
934     }
935     return type;
936 }
937
938 /* Method stubs */
939
940 /*
941  * Constructor
942  */
943
944 NDMPConnection *
945 ndmp_connection_new(
946     gchar *hostname,
947     gint port,
948     gchar *username,
949     gchar *password,
950     gchar *auth)
951 {
952     NDMPConnection *self = NULL;
953     gchar *key = NULL;
954     gchar *errmsg = NULL;
955     struct ndmconn *conn = NULL;
956     int rc;
957     static int next_connid = 1;
958     static GStaticMutex next_connid_mutex = G_STATIC_MUTEX_INIT;
959
960     conn = ndmconn_initialize(NULL, "amanda-server");
961     if (!conn) {
962         errmsg = "could not initialize ndmconn";
963         goto out;
964     }
965
966     /* set up a handler for unexpected messages, which should generally
967      * be notifications */
968     conn->unexpected = ndmconn_unexpected_impl;
969
970     if (ndmconn_connect_host_port(conn, hostname, port, 0) != 0) {
971         errmsg = ndmconn_get_err_msg(conn);
972         ndmconn_destruct(conn);
973         goto out;
974     }
975
976     if (0 == g_ascii_strcasecmp(auth, "void")) {
977         rc = 0; /* don't authenticate */
978     } else if (0 == g_ascii_strcasecmp(auth, "none")) {
979         rc = ndmconn_auth_none(conn);
980     } else if (0 == g_ascii_strcasecmp(auth, "md5")) {
981         rc = ndmconn_auth_md5(conn, username, password);
982     } else if (0 == g_ascii_strcasecmp(auth, "text")) {
983         rc = ndmconn_auth_text(conn, username, password);
984     } else {
985         errmsg = "invalid auth type";
986         goto out;
987     }
988
989     if (rc != 0) {
990         errmsg = ndmconn_get_err_msg(conn);
991         ndmconn_destruct(conn);
992         goto out;
993     }
994
995     if (conn->protocol_version != NDMP4VER) {
996         errmsg = g_strdup_printf("Only NDMPv4 is supported; got NDMPv%d",
997             conn->protocol_version);
998         ndmconn_destruct(conn);
999         goto out;
1000     }
1001
1002     self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL));
1003     self->conn = conn;
1004     g_static_mutex_lock(&next_connid_mutex);
1005     self->connid = next_connid++;
1006     g_static_mutex_unlock(&next_connid_mutex);
1007     conn->context = (void *)self;
1008     g_debug("opening new NDMPConnection #%d: to %s:%d", self->connid, hostname, port);
1009
1010 out:
1011     /* make a "fake" error connection if we have an error message.  Note that
1012      * this object is not added to the instances hash */
1013     if (errmsg) {
1014         self = NDMP_CONNECTION(g_object_new(TYPE_NDMP_CONNECTION, NULL));
1015         self->startup_err = errmsg;
1016         errmsg = NULL;
1017     }
1018
1019     return self;
1020 }