Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Archive.swg
1 /*
2  * Copyright (c) 2008-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 S. Mathilda Ave., Suite 300
18  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19  */
20
21 %module "Amanda::Archive"
22 %include "amglue/amglue.swg"
23 %include "exception.i"
24 %include "cstring.i"
25
26 %include "Amanda/Archive.pod"
27
28 %{
29 #include "amar.h"
30 %}
31
32 %{
33 /* Support code (not directly available from perl) */
34
35 #define AMANDA_ARCHIVE_ERROR_DOMAIN "Amanda archive"
36
37 /* A C object to contain all of the relevant callbacks and other state during a
38  * read operation; this becomes the user_data during the read */
39 typedef struct perl_read_data_s {
40     SV *user_data;
41     SV *file_start_sub;
42     SV *file_finish_sub;
43
44     amar_attr_handling_t *handling_array;
45 } perl_read_data_t;
46
47 static gboolean
48 read_start_file_cb(
49         gpointer user_data,
50         uint16_t filenum,
51         gpointer filename,
52         gsize filename_len,
53         gboolean *ignore,
54         gpointer *file_data)
55 {
56     dSP;
57     perl_read_data_t *dat = user_data;
58     SV *rv = NULL;
59     STRLEN len;
60     int count;
61
62     *file_data = NULL;
63
64     g_assert(dat->file_start_sub != NULL);
65
66     ENTER;
67     SAVETMPS;
68
69     PUSHMARK(SP);
70     XPUSHs(dat->user_data);
71     XPUSHs(sv_2mortal(newSViv(filenum)));
72     XPUSHs(sv_2mortal(newSVpvn(filename, filename_len)));
73     PUTBACK;
74
75     count = call_sv(dat->file_start_sub, G_EVAL|G_SCALAR);
76
77     SPAGAIN;
78
79     if (count != 1)
80         croak("file_start_sub returned nothing");
81
82     rv = POPs;
83
84     /* if it's the string "IGNORE", then ignore it */
85     if (SvPOK(rv)) {
86         static const char *ign = "IGNORE";
87         char *rvstr = SvPV(rv, len);
88         if (strlen(ign) == len && 0 == strncmp(ign, rvstr, len))
89             *ignore = TRUE;
90     }
91
92     /* otherwise, keep the value */
93     if (!*ignore)
94         *(SV **)(file_data) = SvREFCNT_inc(rv);
95
96     PUTBACK;
97     FREETMPS;
98     LEAVE;
99
100     if (SvTRUE(ERRSV))
101         return FALSE;
102     return TRUE;
103 }
104
105 static gboolean
106 read_finish_file_cb(
107         gpointer user_data,
108         uint16_t filenum,
109         gpointer *file_data,
110         gboolean truncated)
111 {
112     dSP;
113     perl_read_data_t *dat = user_data;
114
115     g_assert(dat->file_finish_sub != NULL);
116
117     ENTER;
118     SAVETMPS;
119
120     PUSHMARK(SP); XPUSHs(dat->user_data); XPUSHs(*(SV **)file_data);
121     XPUSHs(sv_2mortal(newSViv(filenum)));
122     XPUSHs(sv_2mortal(newSViv(truncated))); PUTBACK;
123
124     call_sv(dat->file_finish_sub, G_EVAL|G_DISCARD);
125
126     /* we're done with this file's file_data */
127     SvREFCNT_dec(*(SV **)file_data);
128
129     FREETMPS;
130     LEAVE;
131
132     if (SvTRUE(ERRSV))
133         return FALSE;
134     return TRUE;
135 }
136
137 static gboolean
138 read_frag_cb(
139         gpointer user_data,
140         uint16_t filenum,
141         gpointer file_data,
142         uint16_t attrid,
143         gpointer attrid_data,
144         gpointer *attr_data,
145         gpointer data,
146         gsize size,
147         gboolean eoa,
148         gboolean truncated)
149 {
150     dSP;
151     perl_read_data_t *dat = user_data;
152     SV *rv;
153     int count;
154
155     if (!attrid_data)
156         return TRUE;
157
158     ENTER;
159     SAVETMPS;
160
161     PUSHMARK(SP);
162     XPUSHs(dat->user_data);
163     XPUSHs(sv_2mortal(newSViv(filenum)));
164     XPUSHs((SV *)file_data);
165     XPUSHs(sv_2mortal(newSViv(attrid)));
166     if (*attr_data)
167         XPUSHs((SV *)(*attr_data));
168     else
169         XPUSHs(&PL_sv_undef);
170     XPUSHs(sv_2mortal(newSVpvn(data, size)));
171     XPUSHs(sv_2mortal(newSViv(eoa)));
172     XPUSHs(sv_2mortal(newSViv(truncated)));
173     PUTBACK;
174
175     count = call_sv(attrid_data, G_EVAL|G_SCALAR);
176
177     SPAGAIN;
178
179     if (count != 1)
180         croak("fragment callback returned nothing");
181
182     rv = POPs;
183
184     if (eoa) {
185         SvREFCNT_dec(*attr_data);
186     } else {
187         /* increment before decrement here, in case they're the same object */
188         SvREFCNT_inc(rv);
189         SvREFCNT_dec(*attr_data);
190         *attr_data = rv;
191     }
192
193     FREETMPS;
194     LEAVE;
195
196     if (SvTRUE(ERRSV))
197         return FALSE;
198     return TRUE;
199 }
200
201 /* generic function to recognize when a string+len represents a number and
202  * incidentally return the resulting value.  Note that this does not handle
203  * negative numbers. */
204 static gboolean
205 is_number(char *str, int len, int *result)
206 {
207     char *end = str+len;
208     int r = 0;
209
210     while (str < end) {
211         if (!g_ascii_isdigit(*str)) return FALSE;
212         r = r * 10 + (int)(*str - '0');
213         if (r < 0) {
214             /* overflow */
215             return FALSE;
216         }
217         str++;
218     }
219
220     *result = r;
221     return TRUE;
222 }
223
224 %}
225
226 /* Rename all of the below wrapper functions (suffixed with '_') for
227  * consumption by perl */
228 %rename(amar_new) amar_new_;
229 %rename(amar_close) amar_close_;
230 %rename(amar_new_file) amar_new_file_;
231 %rename(amar_file_close) amar_file_close_;
232 %rename(amar_new_attr) amar_new_attr_;
233 %rename(amar_attr_close) amar_attr_close_;
234 %rename(amar_attr_add_data_buffer) amar_attr_add_data_buffer_;
235 %rename(amar_attr_add_data_fd) amar_attr_add_data_fd_;
236 %rename(amar_read) amar_read_;
237
238 /* typemaps for the below */
239 %apply (char *STRING, int LENGTH) { (char *filename, gsize filename_len) };
240 %apply (char *STRING, int LENGTH) { (char *buffer, gsize size) };
241 %typemap(in) SV * "$1 = $input;"
242
243 %typemap(in) off_t *want_position (off_t position) {
244     if (SvTRUE($input)) {
245         position = 0;
246         $1 = &position;
247     } else {
248         $1 = NULL;
249     }
250 }
251 %typemap(argout) off_t *want_position {
252     if ($1) {
253         SP += argvi; PUTBACK;
254         $result = sv_2mortal(amglue_newSVi64(*$1));
255         SPAGAIN; SP -= argvi; argvi++;
256     }
257 }
258
259 %inline %{
260
261 /* Wrapper functions, mostly dealing with error handling */
262
263 amar_t *amar_new_(int fd, char *modestr) {
264     GError *error = NULL;
265     amar_t *rv;
266     int mode;
267
268     if (strcmp(modestr, ">") == 0)
269         mode = O_WRONLY;
270     else if (strcmp(modestr, "<") == 0)
271         mode = O_RDONLY;
272     else
273         croak("mode must be '<' or '>'");
274
275     if ((rv = amar_new(fd, mode, &error))) {
276         return rv;
277     }
278
279     croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
280     return NULL;
281 }
282
283 void amar_close_(amar_t *arch) {
284     GError *error = NULL;
285     if (!amar_close(arch, &error))
286         croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
287 }
288
289 amar_file_t *
290 amar_new_file_(amar_t *arch, char *filename, gsize filename_len, off_t *want_position) {
291     GError *error = NULL;
292     amar_file_t *file;
293     g_assert(arch != NULL);
294
295     file = amar_new_file(arch, filename, filename_len, want_position, &error);
296     if (file)
297         return file;
298
299     croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
300     return NULL;
301 }
302
303 void amar_file_close_(amar_file_t *file) {
304     GError *error = NULL;
305     if (!amar_file_close(file, &error))
306         croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
307 }
308
309 amar_attr_t *
310 amar_new_attr_(amar_file_t *file, guint16 attrid) {
311     GError *error = NULL;
312     amar_attr_t *attr;
313
314     g_assert(file != NULL);
315
316     attr = amar_new_attr(file, attrid, &error);
317     if (attr)
318         return attr;
319
320     croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
321     return NULL;
322 }
323
324 void amar_attr_close_(amar_attr_t *attr) {
325     GError *error = NULL;
326     if (!amar_attr_close(attr, &error))
327         croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
328 }
329
330 void amar_attr_add_data_buffer_(amar_attr_t *attr, char *buffer, gsize size, gboolean eoa) {
331     GError *error = NULL;
332     if (!amar_attr_add_data_buffer(attr, buffer, size, eoa, &error))
333         croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
334 }
335
336 size_t
337 amar_attr_add_data_fd_(amar_attr_t *attr, int fd, gboolean eoa) {
338     GError *error = NULL;
339     size_t rv = amar_attr_add_data_fd(attr, fd, eoa, &error);
340     if (rv < 0)
341         croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
342     return rv;
343 }
344
345 /* reading */
346
347 void amar_read_(amar_t *archive, SV *params_hashref) {
348     perl_read_data_t *dat = g_new0(perl_read_data_t, 1);
349     GError *error = NULL;
350     gboolean success;
351     HV *params;
352     HE *param;
353     I32 len;
354     int maxhandlers;
355     int hdl_idx;
356
357     /* make sure we got a hashref */
358     if (!SvROK(params_hashref) || SvTYPE(SvRV(params_hashref)) != SVt_PVHV)
359         croak("read() expects a single hashref");
360     params = (HV *)SvRV(params_hashref);
361     len = hv_iterinit(params);
362
363     maxhandlers = hdl_idx = len;
364     dat->handling_array = g_new0(amar_attr_handling_t, len+1);
365
366     /* loop through the parameters */
367     while ((param = hv_iternext(params))) {
368         I32 keylen;
369         char *key = hv_iterkey(param, &keylen);
370         int attrid;
371
372         /* if it's a number, it's handling information for an attrid */
373         if (is_number(key, keylen, &attrid)) {
374             SV *val = hv_iterval(params, param);
375             SV *coderef;
376             UV bufsize = 0;
377             int i;
378
379             if (!SvROK(val)) goto croak_hdl;
380
381             switch (SvTYPE(SvRV(val))) {
382                 case SVt_PVCV:
383                     coderef = val;
384                     break;
385
386                 case SVt_PVAV: {
387                     AV *arr = (AV *)SvRV(val);
388                     SV **svp;
389
390                     if (av_len(arr) != 1) /* av_len == largest index, not length */
391                         goto croak_hdl;
392
393                     /* get the bufsize */
394                     svp = av_fetch(arr, 0, 0);
395                     if (!SvIOK(*svp))
396                         goto croak_hdl;
397                     bufsize = SvUV(*svp);
398
399                     /* and the coderef */
400                     svp = av_fetch(arr, 1, 0);
401                     if (!SvROK(*svp) || SvTYPE(SvRV(*svp)) != SVt_PVCV)
402                         goto croak_hdl;
403                     coderef = *svp;
404                     break;
405                 }
406
407                 default:
408                     goto croak_hdl;
409             }
410
411             /* fill in the handling array, putting attrid 0 at the end, and
412              * filling in entries backward from there */
413             i = (attrid == 0)? maxhandlers : --hdl_idx;
414             dat->handling_array[i].attrid = attrid;
415             dat->handling_array[i].min_size = bufsize;
416             dat->handling_array[i].callback = read_frag_cb;
417             dat->handling_array[i].attrid_data = coderef;
418             SvREFCNT_inc(coderef);
419             continue;
420
421         croak_hdl:
422             croak("Expected CODEREF or [ MIN_SIZE, CODEREF ] for attrid %d", attrid);
423         }
424
425 #define key_compare(key, val, keylen) \
426     (keylen == sizeof(val)-1) && (0 == strncmp(key, val, keylen))
427
428         if (key_compare(key, "file_start", keylen)) {
429             SV *val = hv_iterval(params, param);
430             if (!SvROK(val) || SvTYPE(SvRV(val)) != SVt_PVCV)
431                 croak("Expected a CODEREF for file_start");
432             dat->file_start_sub = val;
433             SvREFCNT_inc(val);
434             continue;
435         }
436
437         if (key_compare(key, "file_finish", keylen)) {
438             SV *val = hv_iterval(params, param);
439             if (!SvROK(val) || SvTYPE(SvRV(val)) != SVt_PVCV)
440                 croak("Expected a CODEREF for file_finish");
441             dat->file_finish_sub = val;
442             SvREFCNT_inc(val);
443             continue;
444         }
445
446         if (key_compare(key, "user_data", keylen)) {
447             SV *val = hv_iterval(params, param);
448             dat->user_data = val;
449             SvREFCNT_inc(val);
450             continue;
451         }
452 #undef key_compare
453         croak("Invalid parameter named '%*s'", (int)keylen, key);
454     }
455
456     if (!dat->user_data)
457         dat->user_data = &PL_sv_undef;
458
459     success = amar_read(archive, dat, dat->handling_array + hdl_idx,
460         dat->file_start_sub? read_start_file_cb : NULL,
461         dat->file_finish_sub? read_finish_file_cb : NULL,
462         &error);
463
464     /* now unreference and free everything we referenced earlier */
465     if (dat->file_start_sub)
466         SvREFCNT_dec(dat->file_start_sub);
467     if (dat->file_finish_sub)
468         SvREFCNT_dec(dat->file_finish_sub);
469     if (dat->user_data && dat->user_data != &PL_sv_undef)
470         SvREFCNT_dec(dat->user_data);
471
472     for (hdl_idx = 0; hdl_idx <= maxhandlers; hdl_idx++) {
473         if (dat->handling_array[hdl_idx].attrid_data)
474             SvREFCNT_dec(dat->handling_array[hdl_idx].attrid_data);
475     }
476
477     g_free(dat->handling_array);
478     g_free(dat);
479
480     /* if amar_read returned FALSE, then either we hit an internal
481      * error, or one of the perl callbacks raised an exception, and $@
482      * is still set */
483     if (!success) {
484         if (error)
485             croak_gerror(AMANDA_ARCHIVE_ERROR_DOMAIN, &error);
486         else
487             croak(NULL);
488     }
489 }
490
491 %}
492
493 /* now wrap those flat functions in Perl classes, depending on the perl
494  * refcounting to close objects in the right order */
495
496 %perlcode %{
497 package Amanda::Archive;
498
499 # Expose the Archive constructor at Amanda::Archive->new
500 sub new {
501     my $pkg = shift;
502     Amanda::Archive::Archive->new(@_);
503 }
504
505 package Amanda::Archive::Archive;
506
507 sub new {
508     my ($class, $fd, $mode) = @_;
509     my $arch = Amanda::Archive::amar_new($fd, $mode);
510     return bless (\$arch, $class);
511 }
512
513 sub close {
514     my $self = shift;
515     if ($$self) {
516         Amanda::Archive::amar_close($$self);
517         $$self = undef;
518     }
519 }
520
521 sub DESTROY {
522     my $self = shift;
523     $self->close();
524 }
525
526 sub new_file {
527     my ($self, $filename, $want_offset) = @_;
528     return Amanda::Archive::File->new($self, $filename, $want_offset);
529 }
530
531 sub Amanda::Archive::Archive::read {
532     my $self = shift;
533     die "Archive is not open" unless ($$self);
534     # pass a hashref to the C code
535     my %h = @_;
536     Amanda::Archive::amar_read($$self, \%h);
537 }
538
539 package Amanda::Archive::File;
540
541 sub new {
542     my ($class, $arch, $filename, $want_offset) = @_;
543     die "Archive is not open" unless ($$arch);
544     if ($want_offset) {
545         # note that posn is returned first by the SWIG wrapper
546         my ($file, $posn) = Amanda::Archive::amar_new_file($$arch, $filename, $want_offset);
547         return (bless([ $file, $arch ], $class), $posn);
548     } else {
549         my $file = Amanda::Archive::amar_new_file($$arch, $filename, $want_offset);
550         return bless([ $file, $arch ], $class);
551     }
552 }
553
554 sub close {
555     my $self = shift;
556     if ($self->[0]) {
557         Amanda::Archive::amar_file_close($self->[0]);
558         $self->[0] = undef;
559     }
560 }
561
562 sub DESTROY {
563     my $self = shift;
564     $self->close();
565 }
566
567 sub new_attr {
568     my ($self, $attrid) = @_;
569     return Amanda::Archive::Attr->new($self, $attrid);
570 }
571
572 package Amanda::Archive::Attr;
573
574 sub new {
575     my ($class, $file, $attrid) = @_;
576     die "File is not open" unless ($file->[0]);
577     my $attr = Amanda::Archive::amar_new_attr($file->[0], $attrid);
578     return bless ([$attr, $file], $class);
579 }
580
581 sub close {
582     my $self = shift;
583     if ($self->[0]) {
584         Amanda::Archive::amar_attr_close($self->[0]);
585         $self->[0] = undef;
586     }
587 }
588
589 sub DESTROY {
590     my $self = shift;
591     $self->close();
592 }
593
594 sub add_data {
595     my ($self, $data, $eoa) = @_;
596     die "Attr is not open" unless ($self->[0]);
597     Amanda::Archive::amar_attr_add_data_buffer($self->[0], $data, $eoa);
598 }
599
600 sub add_data_fd {
601     my ($self, $fd, $eoa) = @_;
602     die "Attr is not open" unless ($self->[0]);
603     return Amanda::Archive::amar_attr_add_data_fd($self->[0], $fd, $eoa);
604 }
605 %}