Add myself as author of id3
[platform/upstream/lightmediascanner.git] / src / plugins / id3 / id3.c
1 /**
2  * Copyright (C) 2008 by INdT
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser 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,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
19  * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
20  */
21
22 /**
23  * @brief
24  *
25  * id3 file parser.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #define _GNU_SOURCE
33 #define _XOPEN_SOURCE 600
34 #include <lightmediascanner_plugin.h>
35 #include <lightmediascanner_db.h>
36 #include <lightmediascanner_charset_conv.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <ctype.h>
45
46 #define ID3V2_HEADER_SIZE       10
47 #define ID3V2_FOOTER_SIZE       10
48
49 enum ID3Encodings {
50     ID3_ENCODING_LATIN1 = 0,
51     ID3_ENCODING_UTF16,
52     ID3_ENCODING_UTF16BE,
53     ID3_ENCODING_UTF8,
54     ID3_ENCODING_UTF16LE,
55     ID3_ENCODING_LAST
56 };
57 #define ID3_NUM_ENCODINGS ID3_ENCODING_LAST
58
59
60 #include "id3v1_genres.c"
61
62 struct id3_info {
63     struct lms_string_size title;
64     struct lms_string_size artist;
65     struct lms_string_size album;
66     struct lms_string_size genre;
67     unsigned char trackno;
68     int cur_artist_priority;
69 };
70
71 struct id3v2_frame_header {
72     char frame_id[4];
73     unsigned int frame_size;
74     int compression;
75     int data_length_indicator;
76 };
77
78 struct id3v1_tag {
79     char title[30];
80     char artist[30];
81     char album[30];
82     char year[4];
83     char comments[30];
84     char genre;
85 } __attribute__((packed));
86
87 struct plugin {
88     struct lms_plugin plugin;
89     lms_db_audio_t *audio_db;
90     lms_charset_conv_t *cs_convs[ID3_NUM_ENCODINGS];
91 };
92
93 static const char _name[] = "id3";
94 static const struct lms_string_size _exts[] = {
95     LMS_STATIC_STRING_SIZE(".mp3"),
96     LMS_STATIC_STRING_SIZE(".aac")
97 };
98
99 static unsigned int
100 _to_uint(const char *data, int data_size)
101 {
102     unsigned int sum = 0;
103     unsigned int last, i;
104
105     last = data_size > 4 ? 3 : data_size - 1;
106
107     for (i = 0; i <= last; i++)
108         sum |= ((unsigned char) data[i]) << ((last - i) * 8);
109
110     return sum;
111 }
112
113 static inline int
114 _is_id3v2_second_synch_byte(unsigned char byte)
115 {
116     if (byte == 0xff)
117         return 0;
118     if ((byte & 0xE0) == 0xE0)
119         return 1;
120     return 0;
121 }
122
123 static long
124 _find_id3v2(int fd)
125 {
126     static const char pattern[3] = "ID3";
127     char buffer[3];
128     unsigned int prev_part_match, prev_part_match_sync = 0;
129     long buffer_offset;
130
131     if (read(fd, buffer, sizeof(buffer)) != sizeof(buffer))
132         return -1;
133
134     if (memcmp(buffer, pattern, sizeof(pattern)) == 0)
135         return 0;
136
137     /* This loop is the crux of the find method.  There are three cases that we
138      * want to account for:
139      * (1) The previously searched buffer contained a partial match of the
140      * search pattern and we want to see if the next one starts with the
141      * remainder of that pattern.
142      *
143      * (2) The search pattern is wholly contained within the current buffer.
144      *
145      * (3) The current buffer ends with a partial match of the pattern.  We will
146      * note this for use in the next iteration, where we will check for the rest
147      * of the pattern.
148      */
149     buffer_offset = 0;
150     prev_part_match_sync = 0;
151     prev_part_match = 0;
152     while (1) {
153         const char *p, *p_end;
154
155         /* (1) previous partial match */
156         if (prev_part_match_sync) {
157             if (_is_id3v2_second_synch_byte(buffer[0]))
158                 return -1;
159             prev_part_match_sync = 0;
160         }
161
162         if (prev_part_match) {
163             const int size = sizeof(buffer) - prev_part_match;
164             const char *part_pattern = pattern + prev_part_match;
165
166             if (memcmp(buffer, part_pattern, size) == 0)
167                 return buffer_offset - prev_part_match;
168
169             prev_part_match = 0;
170         }
171
172         p_end = buffer + sizeof(buffer);
173         for (p = buffer; p < p_end; p++) {
174             if (*p == pattern[0]) {
175                 /* Try to match pattern, possible partial contents */
176                 const char *q;
177                 int todo;
178
179                 q = p + 1;
180                 todo = p_end - q;
181                 if (todo == 0 || memcmp(q, pattern + 1, todo) == 0) {
182                     todo++;
183                     if (todo == sizeof(buffer))
184                         /* (2) pattern contained in current buffer */
185                         return buffer_offset;
186
187                     /* (3) partial match */
188                     prev_part_match = todo;
189                     break;
190                 }
191             } else if ((unsigned char)*p == 0xff) {
192                 /* Try to match synch pattern, possible partial contents */
193                 const char *q;
194
195                 q = p + 1;
196                 if (q < p_end) {
197                     if (_is_id3v2_second_synch_byte(*q))
198                         /* (2) synch pattern contained in current buffer */
199                         return -1;
200                 } else
201                     /* (3) partial match */
202                     prev_part_match_sync = 1;
203             }
204         }
205
206         if (read(fd, buffer, sizeof(buffer)) != sizeof(buffer))
207             return -1;
208         buffer_offset += sizeof(buffer);
209     }
210
211     return -1;
212 }
213
214 static unsigned int
215 _get_id3v2_frame_header_size(unsigned int version)
216 {
217     switch (version) {
218     case 0:
219     case 1:
220     case 2:
221         return 6;
222     case 3:
223     case 4:
224     default:
225         return 10;
226     }
227 }
228
229 static void
230 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
231 {
232     switch (version) {
233     case 0:
234     case 1:
235     case 2:
236         memcpy(fh->frame_id, data, 3);
237         fh->frame_id[3] = 0;
238         fh->frame_size = _to_uint(data + 3, 3);
239         fh->compression = 0;
240         fh->data_length_indicator = 0;
241         break;
242     case 3:
243         memcpy(fh->frame_id, data, 4);
244         fh->frame_size = _to_uint(data + 4, 4);
245         fh->compression = data[9] & 0x40;
246         fh->data_length_indicator = 0;
247         break;
248     case 4:
249     default:
250         memcpy(fh->frame_id, data, 4);
251         fh->frame_size = _to_uint(data + 4, 4);
252         fh->compression = data[9] & 0x4;
253         fh->data_length_indicator = data[9] & 0x1;
254         break;
255     }
256 }
257
258 static inline void
259 _get_id3v2_frame_info(const char *frame_data, unsigned int frame_size, struct lms_string_size *s, lms_charset_conv_t *cs_conv, int strip)
260 {
261     if (frame_size > s->len)
262         s->str = realloc(s->str, sizeof(char) * (frame_size + 1));
263     memcpy(s->str, frame_data, frame_size);
264     s->str[frame_size] = '\0';
265     s->len = frame_size;
266     if (cs_conv)
267         lms_charset_conv(cs_conv, &s->str, &s->len);
268     if (strip)
269         lms_string_size_strip_and_free(s);
270 }
271
272 static int
273 _get_id3v2_artist(unsigned int index, const char *frame_data, unsigned int frame_size, struct id3_info *info, lms_charset_conv_t *cs_conv)
274 {
275     static const unsigned char artist_priorities[] = {3, 4, 2, 1};
276     const unsigned int index_max = sizeof(artist_priorities) / sizeof(*artist_priorities);
277
278     if (index >= index_max)
279         return 1;
280
281     if (artist_priorities[index] > info->cur_artist_priority) {
282         struct lms_string_size artist = {0};
283
284         _get_id3v2_frame_info(frame_data, frame_size, &artist, cs_conv, 1);
285         if (artist.str) {
286             if (info->artist.str)
287                 free(info->artist.str);
288             info->artist = artist;
289             info->cur_artist_priority = artist_priorities[index];
290         }
291     }
292     return 0;
293 }
294
295 static int
296 _get_id3v1_genre(unsigned int genre, struct lms_string_size *out)
297 {
298     if (genre < ID3V1_NUM_GENRES) {
299         unsigned int size, base, len;
300
301         base = id3v1_genres_offsets[genre];
302         size = id3v1_genres_offsets[genre + 1] - base;
303         len = size - 1;
304
305         if (len > out->len) {
306             char *p = realloc(out->str, size);
307             if (!p)
308                 return -2;
309             out->str = p;
310         }
311
312         out->len = len;
313         memcpy(out->str, id3v1_genres_mem + base, size);
314
315         return 0;
316     }
317     return -1;
318 }
319
320 static inline int
321 _parse_id3v1_genre(const char *str_genre, struct lms_string_size *out)
322 {
323     return _get_id3v1_genre(atoi(str_genre), out);
324 }
325
326 static void
327 _get_id3v2_genre(const char *frame_data, unsigned int frame_size, struct lms_string_size *out, lms_charset_conv_t *cs_conv)
328 {
329     int i, is_number;
330     struct lms_string_size genre = {0};
331
332     _get_id3v2_frame_info(frame_data, frame_size, &genre, cs_conv, 1);
333
334     if (!genre.str)
335         return;
336
337     is_number = (genre.len != 0 && genre.str[0] != '(');
338     if (is_number) {
339         for (i = 0; i < genre.len; ++i) {
340             if (!isdigit(genre.str[i])) {
341                 is_number = 0;
342                 break;
343             }
344         }
345     }
346
347     if (is_number && _parse_id3v1_genre(genre.str, out) == 0) {
348         /* id3v1 genre found */
349         free(genre.str);
350         return;
351     }
352
353     /* ID3v2.3 "content type" can contain a ID3v1 genre number in parenthesis at
354      * the beginning of the field. If this is all that the field contains, do a
355      * translation from that number to the name and return that.  If there is a
356      * string folloing the ID3v1 genre number, that is considered to be
357      * authoritative and we return that instead. Or finally, the field may
358      * simply be free text, in which case we just return the value. */
359
360     if (genre.len > 1 && genre.str[0] == '(') {
361         char *closing = NULL;
362
363         if (genre.str[genre.len - 1] == ')') {
364             closing = strchr(genre.str, ')');
365             if (closing == genre.str + genre.len - 1) {
366                 /* ) is the last character and only appears once in the
367                  * string get the id3v1 genre enclosed by parentheses
368                  */
369                 if (_parse_id3v1_genre(genre.str + 1, out) == 0) {
370                     free(genre.str);
371                     return;
372                 }
373             }
374         }
375
376         /* get the string followed by the id3v1 genre */
377         if (!closing)
378             closing = strchr(genre.str, ')');
379
380         if (closing) {
381             closing++;
382             out->len = genre.len - (closing - genre.str);
383             out->str = genre.str;
384             memmove(out->str, closing, out->len + 1); /* includes '\0' */
385             lms_string_size_strip_and_free(out);
386             return;
387         }
388     }
389
390     /* pure text */
391     *out = genre;
392 }
393
394 static void
395 _get_id3v2_trackno(const char *frame_data, unsigned int frame_size, struct id3_info *info, lms_charset_conv_t *cs_conv)
396 {
397     struct lms_string_size trackno = {0};
398
399     _get_id3v2_frame_info(frame_data, frame_size, &trackno, cs_conv, 0);
400     info->trackno = atoi(trackno.str);
401     free(trackno.str);
402 }
403
404 static void
405 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct id3_info *info, lms_charset_conv_t **cs_convs)
406 {
407     lms_charset_conv_t *cs_conv;
408     unsigned int text_encoding, frame_size;
409     const char *fid;
410
411 #if 0
412     fprintf(stderr, "frame id = %.4s frame size = %d text encoding = %d\n",
413             fh->frame_id, fh->frame_size, frame_data[0]);
414 #endif
415
416     /* All used frames start with 'T' */
417     fid = fh->frame_id;
418     if (fid[0] != 'T')
419         return;
420
421     /* Latin1  = 0
422      * UTF16   = 1
423      * UTF16BE = 2
424      * UTF8    = 3
425      * UTF16LE = 4
426      */
427     text_encoding = frame_data[0];
428
429     /* skip first byte - text encoding */
430     frame_data += 1;
431     frame_size = fh->frame_size - 1;
432
433     if (text_encoding < ID3_NUM_ENCODINGS) {
434         if (text_encoding == ID3_ENCODING_UTF16) {
435             if (memcmp(frame_data, "\xfe\xff", 2) == 0)
436                 text_encoding = ID3_ENCODING_UTF16BE;
437             else
438                 text_encoding = ID3_ENCODING_UTF16LE;
439             frame_data += 2;
440             frame_size -= 2;
441         }
442         cs_conv = cs_convs[text_encoding];
443     } else
444         cs_conv = NULL;
445
446     /* ID3v2.2 used 3 bytes for the frame id, so let's check it */
447     if ((fid[1] == 'T' && fid[2] == '2') ||
448         (fid[1] == 'I' && fid[2] == 'T' && fid[3] == '2'))
449         _get_id3v2_frame_info(frame_data, frame_size, &info->title, cs_conv, 1);
450     else if (fid[1] == 'P') {
451         if (fid[2] == 'E')
452             _get_id3v2_artist(fid[3] - '1', frame_data, frame_size,
453                               info, cs_conv);
454         else if (fid[2] >= '1' && fid[2] <= '4')
455             _get_id3v2_artist(fid[2] - '1', frame_data, frame_size,
456                               info, cs_conv);
457     }
458     /* TALB, TAL */
459     else if (fid[1] == 'A' && fid[2] == 'L')
460         _get_id3v2_frame_info(frame_data, frame_size, &info->album, cs_conv, 1);
461     /* TCON, TCO */
462     else if (fid[1] == 'C' && fid[2] == 'O')
463         _get_id3v2_genre(frame_data, frame_size, &info->genre, cs_conv);
464     else if (fid[1] == 'R' && (fid[2] == 'K' ||
465                                (fid[2] == 'C' && fid[3] == 'K')))
466         _get_id3v2_trackno(frame_data, frame_size, info, cs_conv);
467 }
468
469 static int
470 _parse_id3v2(int fd, long id3v2_offset, struct id3_info *info, lms_charset_conv_t **cs_convs)
471 {
472     char header_data[10], frame_header_data[10];
473     unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
474     int extended_header, footer_present;
475     struct id3v2_frame_header fh;
476     size_t nread;
477
478     lseek(fd, id3v2_offset, SEEK_SET);
479
480     /* parse header */
481     if (read(fd, header_data, ID3V2_HEADER_SIZE) != ID3V2_HEADER_SIZE)
482         return -1;
483
484     tag_size = _to_uint(header_data + 6, 4);
485     if (tag_size == 0)
486         return -1;
487
488     /* parse frames */
489     major_version = header_data[3];
490
491     frame_data_pos = 0;
492     frame_data_length = tag_size;
493
494     /* check for extended header */
495     extended_header = header_data[5] & 0x20; /* bit 6 */
496     if (extended_header) {
497         /* skip extended header */
498         unsigned int extended_header_size;
499         char extended_header_data[4];
500
501         if (read(fd, extended_header_data, 4) != 4)
502             return -1;
503         extended_header_size = _to_uint(extended_header_data, 4);
504         lseek(fd, extended_header_size - 4, SEEK_CUR);
505         frame_data_pos += extended_header_size;
506         frame_data_length -= extended_header_size;
507     }
508
509     footer_present = header_data[5] & 0x8;   /* bit 4 */
510     if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
511         frame_data_length -= ID3V2_FOOTER_SIZE;
512
513     frame_header_size = _get_id3v2_frame_header_size(major_version);
514     while (frame_data_pos < frame_data_length - frame_header_size) {
515         nread = read(fd, frame_header_data, frame_header_size);
516         if (nread == 0)
517             break;
518
519         if (nread != frame_header_size)
520             return -1;
521
522         if (frame_header_data[0] == 0)
523             break;
524
525         _parse_id3v2_frame_header(frame_header_data, major_version, &fh);
526         if (!fh.frame_size)
527             break;
528
529         if (!fh.compression &&
530             fh.frame_id[0] == 'T' &&
531             memcmp(fh.frame_id, "TXXX", 4) != 0) {
532             char *frame_data;
533
534             if (fh.data_length_indicator)
535                 lseek(fd, 4, SEEK_CUR);
536
537             frame_data = malloc(sizeof(char) * fh.frame_size);
538             if (read(fd, frame_data, fh.frame_size) != fh.frame_size) {
539                 free(frame_data);
540                 return -1;
541             }
542
543             _parse_id3v2_frame(&fh, frame_data, info, cs_convs);
544             free(frame_data);
545         }
546         else {
547             if (fh.data_length_indicator)
548                 lseek(fd, fh.frame_size + 4, SEEK_CUR);
549             else
550                 lseek(fd, fh.frame_size, SEEK_CUR);
551         }
552
553         frame_data_pos += fh.frame_size + frame_header_size;
554     }
555
556     return 0;
557 }
558
559 static int
560 _parse_id3v1(int fd, struct id3_info *info, lms_charset_conv_t *cs_conv)
561 {
562     struct id3v1_tag tag;
563     if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
564         return -1;
565
566     info->title.str = strndup(tag.title, 30);
567     info->title.len = strlen(info->title.str);
568     lms_charset_conv(cs_conv, &info->title.str, &info->title.len);
569     info->artist.str = strndup(tag.artist, 30);
570     info->artist.len = strlen(info->artist.str);
571     lms_charset_conv(cs_conv, &info->artist.str, &info->artist.len);
572     info->album.str = strndup(tag.album, 30);
573     info->album.len = strlen(info->album.str);
574     lms_charset_conv(cs_conv, &info->album.str, &info->album.len);
575     _get_id3v1_genre(tag.genre, &info->genre);
576     if (tag.comments[28] == 0 && tag.comments[29] != 0)
577         info->trackno = (unsigned char) tag.comments[29];
578
579     return 0;
580 }
581
582 static void *
583 _match(struct plugin *p, const char *path, int len, int base)
584 {
585     int i;
586
587     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
588     if (i < 0)
589       return NULL;
590     else
591       return (void*)(i + 1);
592 }
593
594 static int
595 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
596 {
597     struct id3_info info = {{0}, {0}, {0}, {0}, 0, -1};
598     struct lms_audio_info audio_info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
599     int r, fd;
600     long id3v2_offset;
601
602     fd = open(finfo->path, O_RDONLY);
603     if (fd < 0) {
604         perror("open");
605         return -1;
606     }
607
608     id3v2_offset = _find_id3v2(fd);
609     if (id3v2_offset >= 0) {
610 #if 0
611         fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
612                 finfo->path, id3v2_offset);
613 #endif
614         if (_parse_id3v2(fd, id3v2_offset, &info, plugin->cs_convs) != 0) {
615             r = -2;
616             goto done;
617         }
618     }
619     else {
620         char tag[3];
621 #if 0
622         fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n",
623                 finfo->path);
624 #endif
625         /* check for id3v1 tag */
626         if (lseek(fd, -128, SEEK_END) == -1) {
627             r = -3;
628             goto done;
629         }
630
631         if (read(fd, &tag, 3) == -1) {
632             r = -4;
633             goto done;
634         }
635
636         if (memcmp(tag, "TAG", 3) == 0) {
637 #if 0
638             fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
639 #endif
640             if (_parse_id3v1(fd, &info, ctxt->cs_conv) != 0) {
641                 r = -5;
642                 goto done;
643             }
644         }
645     }
646
647     if (!info.title.str) {
648         int ext_idx;
649         ext_idx = ((int)match) - 1;
650         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
651         info.title.str = malloc((info.title.len + 1) * sizeof(char));
652         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
653         info.title.str[info.title.len] = '\0';
654         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
655     }
656
657 #if 0
658     fprintf(stderr, "file %s info\n", finfo->path);
659     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
660     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
661     fprintf(stderr, "\talbum='%s'\n", info.album.str);
662     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
663     fprintf(stderr, "\ttrack number='%d'\n", info.trackno);
664 #endif
665
666     audio_info.id = finfo->id;
667     audio_info.title = info.title;
668     audio_info.artist = info.artist;
669     audio_info.album = info.album;
670     audio_info.genre = info.genre;
671     audio_info.trackno = info.trackno;
672     r = lms_db_audio_add(plugin->audio_db, &audio_info);
673
674   done:
675     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
676     close(fd);
677
678     if (info.title.str)
679         free(info.title.str);
680     if (info.artist.str)
681         free(info.artist.str);
682     if (info.album.str)
683         free(info.album.str);
684     if (info.genre.str)
685         free(info.genre.str);
686
687     return r;
688 }
689
690 static int
691 _setup(struct plugin *plugin, struct lms_context *ctxt)
692 {
693     int i;
694     const char *id3_encodings[ID3_NUM_ENCODINGS] = {
695         "Latin1",
696         NULL, /* UTF-16 */
697         "UTF-16BE",
698         NULL, /* UTF-8 */
699         "UTF-16LE",
700     };
701
702     plugin->audio_db = lms_db_audio_new(ctxt->db);
703     if (!plugin->audio_db)
704         return -1;
705
706     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
707         /* do not create charset conv for UTF-8 encoding */
708         if (!id3_encodings[i]) {
709             plugin->cs_convs[i] = NULL;
710             continue;
711         }
712         plugin->cs_convs[i] = lms_charset_conv_new_full(0, 0);
713         if (!plugin->cs_convs[i])
714             return -1;
715         lms_charset_conv_add(plugin->cs_convs[i], id3_encodings[i]);
716     }
717
718     return 0;
719 }
720
721 static int
722 _start(struct plugin *plugin, struct lms_context *ctxt)
723 {
724     return lms_db_audio_start(plugin->audio_db);
725 }
726
727 static int
728 _finish(struct plugin *plugin, struct lms_context *ctxt)
729 {
730     int i;
731
732     if (plugin->audio_db)
733         lms_db_audio_free(plugin->audio_db);
734
735     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
736         if (plugin->cs_convs[i])
737             lms_charset_conv_free(plugin->cs_convs[i]);
738     }
739
740     return 0;
741 }
742
743 static int
744 _close(struct plugin *plugin)
745 {
746     free(plugin);
747     return 0;
748 }
749
750 API struct lms_plugin *
751 lms_plugin_open(void)
752 {
753     struct plugin *plugin;
754
755     plugin = (struct plugin *)malloc(sizeof(*plugin));
756     plugin->plugin.name = _name;
757     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
758     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
759     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
760     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
761     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
762     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
763
764     return (struct lms_plugin *)plugin;
765 }