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