1 /*
2  * Copyright (c) 2009 - 2011 Thomas Schmitt
3  *
4  * This file is part of the libisofs project; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License version 2
6  * or later as published by the Free Software Foundation.
7  * See COPYING file for details.
8  *
9  * It implements a filter facility which can pipe a IsoStream into an external
10  * process, read its output and forward it as IsoStream output to an IsoFile.
11  * The external processes get started according to an IsoExternalFilterCommand
12  * which is described in libisofs.h.
13  *
14  */
15 
16 #ifdef HAVE_CONFIG_H
17 #include "../config.h"
18 #endif
19 
20 #include "../libisofs.h"
21 #include "../filter.h"
22 #include "../fsource.h"
23 #include "../stream.h"
24 
25 #include <sys/types.h>
26 #include <sys/time.h>
27 #include <sys/wait.h>
28 #include <unistd.h>
29 #include <signal.h>
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <string.h>
34 
35 #ifdef Libisofs_external_filters_selecT
36 #include <sys/select.h>
37 #endif
38 
39 /*
40  * A filter that starts an external process and uses its stdin and stdout
41  * for classical pipe filtering.
42  */
43 
44 /* IMPORTANT: Any change must be reflected by extf_clone_stream() */
45 /*
46  * Individual runtime properties exist only as long as the stream is opened.
47  */
48 typedef struct
49 {
50     int send_fd;
51     int recv_fd;
52     pid_t pid;
53     off_t in_counter;
54     int in_eof;
55     off_t out_counter;
56     int out_eof;
57     uint8_t pipebuf[2048]; /* buffers in case of EAGAIN on write() */
58     int pipebuf_fill;
59 } ExternalFilterRuntime;
60 
61 
62 static
extf_running_new(ExternalFilterRuntime ** running,int send_fd,int recv_fd,pid_t child_pid,int flag)63 int extf_running_new(ExternalFilterRuntime **running, int send_fd, int recv_fd,
64                      pid_t child_pid, int flag)
65 {
66     ExternalFilterRuntime *o;
67     *running = o = calloc(sizeof(ExternalFilterRuntime), 1);
68     if (o == NULL) {
69         return ISO_OUT_OF_MEM;
70     }
71     o->send_fd = send_fd;
72     o->recv_fd = recv_fd;
73     o->pid = child_pid;
74     o->in_counter = 0;
75     o->in_eof = 0;
76     o->out_counter = 0;
77     o->out_eof = 0;
78     memset(o->pipebuf, 0, sizeof(o->pipebuf));
79     o->pipebuf_fill = 0;
80     return 1;
81 }
82 
83 
84 /*
85  * The data payload of an individual IsoStream from External Filter
86  */
87 typedef struct
88 {
89     ino_t id;
90 
91     IsoStream *orig;
92 
93     IsoExternalFilterCommand *cmd;
94 
95     off_t size; /* -1 means that the size is unknown yet */
96 
97     ExternalFilterRuntime *running; /* is non-NULL when open */
98 
99 } ExternalFilterStreamData;
100 
101 
102 /* Each individual ExternalFilterStreamData needs a unique id number. */
103 /* >>> This is very suboptimal:
104        The counter can rollover.
105 */
106 static ino_t extf_ino_id = 0;
107 
108 
109 /* <<< */
110 static int print_fd= 0;
111 
112 
113 /*
114  * Methods for the IsoStreamIface of an External Filter object.
115  */
116 
117 
118 /*
119  * @param flag  bit0= original stream is not open
120  */
121 static
extf_stream_close_flag(IsoStream * stream,int flag)122 int extf_stream_close_flag(IsoStream *stream, int flag)
123 {
124     int ret, status;
125     ExternalFilterStreamData *data;
126 
127     if (stream == NULL) {
128         return ISO_NULL_POINTER;
129     }
130     data = stream->data;
131 
132     if (data->running == NULL) {
133         return 1;
134     }
135 
136     /* <<< */
137     if (print_fd) {
138         fprintf(stderr, "libisofs_DEBUG: filter close  in  = %d , ic= %.f\n",
139                 data->running->recv_fd, (double) data->running->in_counter);
140         fprintf(stderr, "libisofs_DEBUG: filter close  out = %d , oc= %.f\n",
141                 data->running->send_fd, (double) data->running->out_counter);
142     }
143 
144     if(data->running->recv_fd != -1)
145         close(data->running->recv_fd);
146     if(data->running->send_fd != -1)
147         close(data->running->send_fd);
148 
149     ret = waitpid(data->running->pid, &status, WNOHANG);
150     if (ret == 0 && data->running->pid != 0) {
151         kill(data->running->pid, SIGKILL);
152         waitpid(data->running->pid, &status, 0);
153     }
154     free(data->running);
155     data->running = NULL;
156     if (flag & 1)
157         return 1;
158     return iso_stream_close(data->orig);
159 }
160 
161 
162 static
extf_stream_close(IsoStream * stream)163 int extf_stream_close(IsoStream *stream)
164 {
165     return extf_stream_close_flag(stream, 0);
166 }
167 
168 
169 /*
170  * @param flag  bit0= do not run .get_size() if size is < 0
171  */
172 static
extf_stream_open_flag(IsoStream * stream,int flag)173 int extf_stream_open_flag(IsoStream *stream, int flag)
174 {
175     ExternalFilterStreamData *data;
176     ExternalFilterRuntime *running = NULL;
177     pid_t child_pid;
178     int send_pipe[2], recv_pipe[2], ret, stream_open = 0;
179 
180     send_pipe[0] = send_pipe[1] = recv_pipe[0] = recv_pipe[1] = -1;
181 
182     if (stream == NULL) {
183         return ISO_NULL_POINTER;
184     }
185     data = (ExternalFilterStreamData*)stream->data;
186     if (data->running != NULL) {
187         return ISO_FILE_ALREADY_OPENED;
188     }
189     if (data->size < 0 && !(flag & 1)) {
190       /* Do the size determination run now, so that the size gets cached
191          and .get_size() will not fail on an opened stream.
192       */
193       stream->class->get_size(stream);
194     }
195 
196     ret = pipe(send_pipe);
197     if (ret == -1) {
198         ret = ISO_OUT_OF_MEM;
199         goto parent_failed;
200     }
201     ret = pipe(recv_pipe);
202     if (ret == -1) {
203         ret = ISO_OUT_OF_MEM;
204         goto parent_failed;
205     }
206 
207     child_pid= fork();
208     if (child_pid == -1) {
209         ret = ISO_DATA_SOURCE_FATAL;
210         goto parent_failed;
211     }
212 
213     if (child_pid != 0) {
214         /* parent */
215         ret = extf_running_new(&running, send_pipe[1], recv_pipe[0], child_pid,
216                                0);
217         if (ret < 0) {
218             goto parent_failed;
219         }
220         data->running = running;
221 
222         /* <<< */
223         if (print_fd) {
224             fprintf(stderr, "libisofs_DEBUG: filter parent in  = %d\n",
225                     data->running->recv_fd);
226             fprintf(stderr, "libisofs_DEBUG: filter parent out = %d\n",
227                     data->running->send_fd);
228         }
229 
230         /* Give up the child-side pipe ends */
231         close(send_pipe[0]);
232         close(recv_pipe[1]);
233 
234         /* Open stream only after forking so that the child does not know
235            the pipe inlets of eventually underlying other filter streams.
236            They would stay open and prevent those underlying filter children
237            from seeing EOF at their input.
238         */
239         ret = iso_stream_open(data->orig);
240 
241 
242         /* <<< TEST <<<
243         ret= ISO_FILE_READ_ERROR;
244         */
245 
246         if (ret < 0) {
247             /* Dispose pipes and child */
248             extf_stream_close_flag(stream, 1);
249             return ret;
250         }
251         stream_open = 1;
252         /* Make filter outlet non-blocking */
253         ret = fcntl(recv_pipe[0], F_GETFL);
254         if (ret != -1) {
255             ret |= O_NONBLOCK;
256             fcntl(recv_pipe[0], F_SETFL, ret);
257         }
258         /* Make filter sink non-blocking */
259         ret = fcntl(send_pipe[1], F_GETFL);
260         if (ret != -1) {
261             ret |= O_NONBLOCK;
262             fcntl(send_pipe[1], F_SETFL, ret);
263         }
264         return 1;
265     }
266 
267     /* child */
268 
269     /* Give up the parent-side pipe ends */
270     close(send_pipe[1]);
271     close(recv_pipe[0]);
272 
273     /* attach pipe ends to stdin and stdout */;
274     close(0);
275     ret = dup2(send_pipe[0], 0);
276     if (ret == -1) {
277         goto child_failed;
278     }
279     close(1);
280     ret = dup2(recv_pipe[1], 1);
281     if (ret == -1) {
282         goto child_failed;
283     }
284 
285     /* <<< */
286     if (print_fd) {
287         fprintf(stderr, "libisofs_DEBUG: filter child  in  = %d\n",
288                 send_pipe[0]);
289         fprintf(stderr, "libisofs_DEBUG: filter child  out = %d\n",
290                 recv_pipe[1]);
291     }
292 
293     /* Self conversion into external program */
294     execv(data->cmd->path, data->cmd->argv); /* should never come back */
295 
296 child_failed:;
297     fprintf(stderr,"--- execution of external filter command failed:\n");
298     fprintf(stderr,"    %s\n", data->cmd->path);
299     exit(127);
300 
301 parent_failed:;
302 
303     /* <<< */
304     if (print_fd) {
305         fprintf(stderr, "libisofs_DEBUG: FAILED : filter parent in  = %d\n",
306                 recv_pipe[0]);
307         fprintf(stderr, "libisofs_DEBUG: FAILED : filter parent out = %d\n",
308                 send_pipe[1]);
309     }
310 
311     if (stream_open)
312         iso_stream_close(data->orig);
313     if(send_pipe[0] != -1)
314         close(send_pipe[0]);
315     if(send_pipe[1] != -1)
316         close(send_pipe[1]);
317     if(recv_pipe[0] != -1)
318         close(recv_pipe[0]);
319     if(recv_pipe[1] != -1)
320         close(recv_pipe[1]);
321     return ret;
322 }
323 
324 
325 static
extf_stream_open(IsoStream * stream)326 int extf_stream_open(IsoStream *stream)
327 {
328     return extf_stream_open_flag(stream, 0);
329 }
330 
331 
332 #ifdef Libisofs_external_filters_selecT
333 
334 /* Performance is weaker than with non-blocking i/o and usleep(). */
335 
336 static
extf_wait_for_io(int fd_in,int fd_out,int microsec,int flag)337 int extf_wait_for_io(int fd_in, int fd_out, int microsec, int flag)
338 {
339     struct timeval wt;
340     fd_set rds,wts,exs;
341     int ready, fd_max;
342 
343     fd_max = fd_out;
344     if (fd_in > fd_out)
345         fd_max = fd_in;
346 
347     FD_ZERO(&rds);
348     FD_ZERO(&wts);
349     FD_ZERO(&exs);
350     if (fd_in >= 0) {
351         FD_SET(fd_in,&rds);
352         FD_SET(fd_in,&exs);
353     }
354     if (fd_out >= 0) {
355         FD_SET(fd_out,&rds);
356         FD_SET(fd_in,&exs);
357     }
358     wt.tv_sec =  microsec/1000000;
359     wt.tv_usec = microsec%1000000;
360     ready = select(fd_max + 1, &rds, &wts, &exs, &wt);
361     if (ready <= 0)
362         return 0;
363     if (fd_in >= 0) {
364         if (FD_ISSET(fd_in, &rds))
365             return 1;
366     }
367     if (fd_out >= 0) {
368         if (FD_ISSET(fd_out, &rds))
369             return 2;
370     }
371     if (fd_in >= 0) {
372         if (FD_ISSET(fd_in, &exs))
373             return -1;
374     }
375     if (fd_out >= 0) {
376         if (FD_ISSET(fd_out, &exs))
377             return -2;
378     }
379     return(0);
380 }
381 
382 #endif /* Libisofs_external_filters_selecT */
383 
384 
385 static
extf_stream_read(IsoStream * stream,void * buf,size_t desired)386 int extf_stream_read(IsoStream *stream, void *buf, size_t desired)
387 {
388     int ret, blocking = 0;
389     ExternalFilterStreamData *data;
390     ExternalFilterRuntime *running;
391     size_t fill = 0;
392 
393     if (stream == NULL) {
394         return ISO_NULL_POINTER;
395     }
396     data = stream->data;
397     running= data->running;
398     if (running == NULL) {
399         return ISO_FILE_NOT_OPENED;
400     }
401     if (running->out_eof) {
402         return 0;
403     }
404 
405     while (1) {
406         if (running->in_eof && !blocking) {
407             /* Make filter outlet blocking */
408             ret = fcntl(running->recv_fd, F_GETFL);
409             if (ret != -1) {
410                 ret &= ~O_NONBLOCK;
411                 fcntl(running->recv_fd, F_SETFL, ret);
412             }
413             blocking = 1;
414         }
415 
416         /* Try to read desired amount from filter */;
417         while (1) {
418             ret = read(running->recv_fd, ((char *) buf) + fill,
419                        desired - fill);
420             if (ret < 0) {
421                 if (errno == EAGAIN)
422         break;
423                 return ISO_FILE_READ_ERROR;
424             }
425             fill += ret;
426             if (ret == 0) {
427                 running->out_eof = 1;
428             }
429             if (ret == 0 || fill >= desired) {
430                 running->out_counter += fill;
431                 return fill;
432             }
433         }
434 
435         if (running->in_eof) {
436             usleep(1000); /* just in case it is still non-blocking */
437     continue;
438         }
439         if (running->pipebuf_fill) {
440             ret = running->pipebuf_fill;
441             running->pipebuf_fill = 0;
442         } else {
443             ret = iso_stream_read(data->orig, running->pipebuf,
444                                   sizeof(running->pipebuf));
445             if (ret > 0)
446                 running->in_counter += ret;
447         }
448         if (ret < 0) {
449             running->in_eof = 1;
450             return ret;
451         }
452         if (ret == 0) {
453 
454             /* <<< */
455             if (print_fd) {
456                 fprintf(stderr,
457                  "libisofs_DEBUG: filter close  out = %d , ic= %.f\n",
458                  running->send_fd, (double) running->in_counter);
459             }
460 
461             running->in_eof = 1;
462             close(running->send_fd); /* Tell the filter: it is over */
463             running->send_fd = -1;
464         } else {
465             running->pipebuf_fill = ret;
466             ret = write(running->send_fd, running->pipebuf,
467                         running->pipebuf_fill);
468             if (ret == -1) {
469                 if (errno == EAGAIN) {
470 
471 #ifdef Libisofs_external_filters_selecT
472 
473                     /* This select() based waiting saves 10 % CPU load but
474                        needs 50 % more real time */
475 
476                     ret = extf_wait_for_io(running->recv_fd, running->send_fd,
477                                            100000, 0);
478                     if (ret < 0)
479                         usleep(1000); /* To make sure sufficient laziness */
480 
481 #else
482 
483                     /* No sleeping needs 90 % more CPU and saves 6 % time */
484                     usleep(1000); /* go lazy because the filter is slow */
485 
486 #endif /* ! Libisofs_external_filters_selecT */
487 
488     continue;
489                 }
490 
491                 /* From the view of the caller it _is_ a read error */
492                 running->in_eof = 1;
493                 return ISO_FILE_READ_ERROR;
494             }
495             running->pipebuf_fill = 0;
496         }
497     }
498     return ISO_FILE_READ_ERROR; /* should never be hit */
499 }
500 
501 
502 static
extf_stream_get_size(IsoStream * stream)503 off_t extf_stream_get_size(IsoStream *stream)
504 {
505     int ret, ret_close;
506     off_t count = 0;
507     ExternalFilterStreamData *data;
508     char buf[64 * 1024];
509     size_t bufsize = 64 * 1024;
510 
511     if (stream == NULL) {
512         return ISO_NULL_POINTER;
513     }
514     data = stream->data;
515 
516     if (data->size >= 0) {
517         return data->size;
518     }
519 
520     /* Run filter command and count output bytes */
521     ret = extf_stream_open_flag(stream, 1);
522     if (ret < 0) {
523         return ret;
524     }
525     while (1) {
526         ret = extf_stream_read(stream, buf, bufsize);
527         if (ret <= 0)
528             break;
529         count += ret;
530     }
531     ret_close = extf_stream_close(stream);
532     if (ret < 0)
533         return ret;
534     if (ret_close < 0)
535         return ret_close;
536 
537     data->size = count;
538     return count;
539 }
540 
541 
542 static
extf_stream_is_repeatable(IsoStream * stream)543 int extf_stream_is_repeatable(IsoStream *stream)
544 {
545     /* Only repeatable streams are accepted as orig */
546     return 1;
547 }
548 
549 
550 static
extf_stream_get_id(IsoStream * stream,unsigned int * fs_id,dev_t * dev_id,ino_t * ino_id)551 void extf_stream_get_id(IsoStream *stream, unsigned int *fs_id,
552                         dev_t *dev_id, ino_t *ino_id)
553 {
554     ExternalFilterStreamData *data;
555 
556     data = stream->data;
557     *fs_id = ISO_FILTER_FS_ID;
558     *dev_id = ISO_FILTER_EXTERNAL_DEV_ID;
559     *ino_id = data->id;
560 }
561 
562 
563 static
extf_stream_free(IsoStream * stream)564 void extf_stream_free(IsoStream *stream)
565 {
566     ExternalFilterStreamData *data;
567 
568     if (stream == NULL) {
569         return;
570     }
571     data = stream->data;
572     if (data->running != NULL) {
573         extf_stream_close(stream);
574     }
575     iso_stream_unref(data->orig);
576     if (data->cmd->refcount > 0)
577         data->cmd->refcount--;
578     free(data);
579 }
580 
581 
582 static
extf_update_size(IsoStream * stream)583 int extf_update_size(IsoStream *stream)
584 {
585     /* By principle size is determined only once */
586     return 1;
587 }
588 
589 
590 static
extf_get_input_stream(IsoStream * stream,int flag)591 IsoStream *extf_get_input_stream(IsoStream *stream, int flag)
592 {
593     ExternalFilterStreamData *data;
594 
595     if (stream == NULL) {
596         return NULL;
597     }
598     data = stream->data;
599     return data->orig;
600 }
601 
602 static
extf_clone_stream(IsoStream * old_stream,IsoStream ** new_stream,int flag)603 int extf_clone_stream(IsoStream *old_stream, IsoStream **new_stream, int flag)
604 {
605     int ret;
606     IsoStream *new_input_stream, *stream;
607     ExternalFilterStreamData *stream_data, *old_stream_data;
608 
609     if (flag)
610         return ISO_STREAM_NO_CLONE; /* unknown option required */
611 
612     stream_data = calloc(1, sizeof(ExternalFilterStreamData));
613     if (stream_data == NULL)
614         return ISO_OUT_OF_MEM;
615     ret = iso_stream_clone_filter_common(old_stream, &stream,
616                                          &new_input_stream, 0);
617     if (ret < 0) {
618         free((char *) stream_data);
619         return ret;
620     }
621     old_stream_data = (ExternalFilterStreamData *) old_stream->data;
622     stream_data->id = ++extf_ino_id;
623     stream_data->orig = new_input_stream;
624     stream_data->cmd = old_stream_data->cmd;
625     stream_data->cmd->refcount++;
626     stream_data->size = old_stream_data->size;
627     stream_data->running = NULL;
628     stream->data = stream_data;
629     *new_stream = stream;
630     return ISO_SUCCESS;
631 }
632 
633 static
634 int extf_cmp_ino(IsoStream *s1, IsoStream *s2);
635 /* Function is defined after definition of extf_stream_class */
636 
637 
638 IsoStreamIface extf_stream_class = {
639     4,
640     "extf",
641     extf_stream_open,
642     extf_stream_close,
643     extf_stream_get_size,
644     extf_stream_read,
645     extf_stream_is_repeatable,
646     extf_stream_get_id,
647     extf_stream_free,
648     extf_update_size,
649     extf_get_input_stream,
650     extf_cmp_ino,
651     extf_clone_stream
652 };
653 
654 
655 static
extf_cmp_ino(IsoStream * s1,IsoStream * s2)656 int extf_cmp_ino(IsoStream *s1, IsoStream *s2)
657 {
658     int i;
659     ExternalFilterStreamData *data1, *data2;
660     IsoExternalFilterCommand *cmd1, *cmd2;
661 
662     /* This function may rely on being called by iso_stream_cmp_ino()
663        only with s1, s2 which both point to it as their .cmp_ino() function.
664        It would be a programming error to let any other than extf_stream_class
665        point to extf_cmp_ino(). This fallback endangers transitivity of
666        iso_stream_cmp_ino().
667     */
668     if (s1->class != &extf_stream_class || s2->class != &extf_stream_class)
669         return iso_stream_cmp_ino(s1, s2, 1);
670 
671     data1 = (ExternalFilterStreamData*) s1->data;
672     data2 = (ExternalFilterStreamData*) s2->data;
673     cmd1 = data1->cmd;
674     cmd2 = data2->cmd;
675     if (cmd1 != cmd2) {
676         if (strcmp(cmd1->name, cmd2->name) != 0)
677             return strcmp(cmd1->name, cmd2->name);
678         if (strcmp(cmd1->path, cmd2->path) != 0)
679             return strcmp(cmd1->path, cmd2->path);
680         if (cmd1->argc != cmd2->argc)
681             return cmd1->argc < cmd2->argc ? -1 : 1;
682         for (i = 0; i < cmd1->argc; i++) {
683             if (strcmp(cmd1->argv[i], cmd2->argv[i]) != 0)
684                 return strcmp(cmd1->argv[i], cmd2->argv[i]);
685         }
686         if (cmd1->behavior != cmd2->behavior)
687             return cmd1->behavior < cmd2->behavior ? -1 : 1;
688         if (strcmp(cmd1->suffix, cmd2->suffix) != 0)
689             return strcmp(cmd1->suffix, cmd2->suffix);
690     }
691 
692     /* Both streams apply the same treatment to their input streams */
693     return iso_stream_cmp_ino(data1->orig, data2->orig, 0);
694 }
695 
696 
697 /* ------------------------------------------------------------------------- */
698 
699 static
extf_filter_free(FilterContext * filter)700 void extf_filter_free(FilterContext *filter)
701 {
702     /* no data are allocated */;
703 }
704 
705 
706 /* To be called by iso_file_add_filter().
707  * The FilterContext input parameter is not furtherly needed for the
708  * emerging IsoStream.
709  */
710 static
extf_filter_get_filter(FilterContext * filter,IsoStream * original,IsoStream ** filtered)711 int extf_filter_get_filter(FilterContext *filter, IsoStream *original,
712                   IsoStream **filtered)
713 {
714     IsoStream *str;
715     ExternalFilterStreamData *data;
716     IsoExternalFilterCommand *cmd;
717 
718     if (filter == NULL || original == NULL || filtered == NULL) {
719         return ISO_NULL_POINTER;
720     }
721     cmd = (IsoExternalFilterCommand *) filter->data;
722     if (cmd->refcount + 1 <= 0) {
723         return ISO_EXTF_TOO_OFTEN;
724     }
725 
726     str = malloc(sizeof(IsoStream));
727     if (str == NULL) {
728         return ISO_OUT_OF_MEM;
729     }
730     data = malloc(sizeof(ExternalFilterStreamData));
731     if (data == NULL) {
732         free(str);
733         return ISO_OUT_OF_MEM;
734     }
735 
736 
737     /* These data items are not owned by this filter object */
738     data->id = ++extf_ino_id;
739     data->orig = original;
740     data->cmd = cmd;
741     data->size = -1;
742     data->running = NULL;
743 
744     /* get reference to the source */
745     iso_stream_ref(data->orig);
746 
747     str->refcount = 1;
748     str->data = data;
749     str->class = &extf_stream_class;
750 
751     *filtered = str;
752 
753     cmd->refcount++;
754     return ISO_SUCCESS;
755 }
756 
757 
758 /* Produce a parameter object suitable for iso_file_add_filter().
759  * It may be disposed by free() after all those calls are made.
760  *
761  * This is an internal call of libisofs to be used by an API call that
762  * attaches an IsoExternalFilterCommand to one or more IsoFile objects.
763  * See libisofs.h for IsoExternalFilterCommand.
764  */
765 static
extf_create_context(IsoExternalFilterCommand * cmd,FilterContext ** filter,int flag)766 int extf_create_context(IsoExternalFilterCommand *cmd,
767                         FilterContext **filter, int flag)
768 {
769     FilterContext *f;
770 
771     *filter = f = calloc(1, sizeof(FilterContext));
772     if (f == NULL) {
773         return ISO_OUT_OF_MEM;
774     }
775     f->refcount = 1;
776     f->version = 0;
777     f->data = cmd;
778     f->free = extf_filter_free;
779     f->get_filter = extf_filter_get_filter;
780     return ISO_SUCCESS;
781 }
782 
783 
784 /*
785  * A function which adds a filter to an IsoFile shall create a temporary
786  * FilterContext by iso_extf_create_context(), use it in one or more calls
787  * of filter.c:iso_file_add_filter() and finally dispose it by free().
788  */
789 
iso_file_add_external_filter(IsoFile * file,IsoExternalFilterCommand * cmd,int flag)790 int iso_file_add_external_filter(IsoFile *file, IsoExternalFilterCommand *cmd,
791                                  int flag)
792 {
793     int ret;
794     FilterContext *f = NULL;
795     IsoStream *stream;
796     off_t original_size = 0, filtered_size = 0;
797 
798     if (cmd->behavior & (1 | 2 | 4)) {
799         original_size = iso_file_get_size(file);
800         if (original_size <= 0 ||
801             ((cmd->behavior & 4) && original_size <= 2048)) {
802             return 2;
803         }
804     }
805     ret = extf_create_context(cmd, &f, 0);
806     if (ret < 0) {
807         return ret;
808     }
809     ret = iso_file_add_filter(file, f, 0);
810     free(f);
811     if (ret < 0) {
812         return ret;
813     }
814     /* Run a full filter process getsize so that the size is cached */
815     stream = iso_file_get_stream(file);
816     filtered_size = iso_stream_get_size(stream);
817     if (filtered_size < 0) {
818         iso_file_remove_filter(file, 0);
819         return filtered_size;
820     }
821     if (((cmd->behavior & 2) && filtered_size >= original_size) ||
822         ((cmd->behavior & 4) && filtered_size / 2048 >= original_size / 2048)){
823         ret = iso_file_remove_filter(file, 0);
824         if (ret < 0) {
825             return ret;
826         }
827         return 2;
828     }
829     return ISO_SUCCESS;
830 }
831 
832 
iso_stream_get_external_filter(IsoStream * stream,IsoExternalFilterCommand ** cmd,int flag)833 int iso_stream_get_external_filter(IsoStream *stream,
834                                    IsoExternalFilterCommand **cmd, int flag)
835 {
836     ExternalFilterStreamData *data;
837 
838     if (stream->class != &extf_stream_class)
839         return 0;
840     data = stream->data;
841     *cmd = data->cmd;
842     return 1;
843 }
844 
845