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