Sanitized memory allocation scheme.
[ruby-vorbistagger.git] / ext / vcedit.c
index b6c7dfd8d0d0db18e361b0fd79b37fa74a1664cb..832f417b2b30519cba6661b3dfb063c52a43f925 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include <stdio.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 
 #define CHUNKSIZE 4096
 
-static int vcedit_open (vcedit_state *state);
-
 struct vcedit_state_St {
        int refcount;
 
-       ogg_sync_state *oy;
-       ogg_stream_state *os;
+       ogg_sync_state oy;
+       ogg_stream_state os;
 
-       vorbis_comment *vc;
-       vorbis_info *vi;
+       vorbis_comment vc;
+       vorbis_info vi;
 
        char filename[PATH_MAX];
 
        FILE *in;
+       bool opened;
        long serial;
        unsigned char *mainbuf;
        unsigned char *bookbuf;
        int     mainlen;
        int     booklen;
-       const char *lasterror;
        char *vendor;
        int prevW;
        int extrapage;
        int eosin;
 };
 
+static void
+vcedit_state_free (vcedit_state *state)
+{
+       free (state->mainbuf);
+       free (state->bookbuf);
+       free (state->vendor);
+
+       if (state->in) {
+               fclose (state->in);
+               state->in = NULL;
+       }
+
+       free (state);
+}
+
+static bool
+vcedit_state_init (vcedit_state *state)
+{
+       state->refcount = 1;
+
+       return true;
+}
+
 vcedit_state *
 vcedit_state_new (const char *filename)
 {
@@ -67,53 +89,31 @@ vcedit_state_new (const char *filename)
 
        memset (state, 0, sizeof (vcedit_state));
 
-       state->refcount = 1;
+       if (!vcedit_state_init (state)) {
+               vcedit_state_free (state);
+               return NULL;
+       }
 
        snprintf (state->filename, sizeof (state->filename),
                  "%s", filename);
 
-       state->in = fopen (state->filename, "rb");
-
-       if (vcedit_open (state) < 0) {
-               free (state);
-               return NULL;
-       }
-
        return state;
 }
 
-const char *
-vcedit_error (vcedit_state *state)
-{
-       return state->lasterror;
-}
-
 vorbis_comment *
 vcedit_comments (vcedit_state *state)
 {
-       return state->vc;
+       return state->opened ? &state->vc : NULL;
 }
 
 static void
 vcedit_clear_internals (vcedit_state *state)
 {
-       if (state->vc) {
-               vorbis_comment_clear (state->vc);
-               free (state->vc);
-               state->vc = NULL;
-       }
+       ogg_stream_clear (&state->os);
+       ogg_sync_clear (&state->oy);
 
-       if (state->os) {
-               ogg_stream_clear (state->os);
-               free (state->os);
-               state->os = NULL;
-       }
-
-       if (state->oy) {
-               ogg_sync_clear (state->oy);
-               free (state->oy);
-               state->oy = NULL;
-       }
+       vorbis_info_clear (&state->vi);
+       vorbis_comment_clear (&state->vc);
 
        free (state->vendor);
        state->vendor = NULL;
@@ -126,14 +126,8 @@ vcedit_clear_internals (vcedit_state *state)
        state->bookbuf = NULL;
        state->booklen = 0;
 
-       if (state->vi) {
-               vorbis_info_clear (state->vi);
-               free (state->vi);
-               state->vi = NULL;
-       }
-
        state->serial = 0;
-       state->prevW = state->extrapage = state->eosin = 0;
+       state->opened = false;
 }
 
 void
@@ -145,13 +139,13 @@ vcedit_state_ref (vcedit_state *state)
 void
 vcedit_state_unref (vcedit_state *state)
 {
-       state->refcount--;
+       if (--state->refcount)
+               return;
 
-       if (!state->refcount) {
-               fclose (state->in);
+       if (state->opened)
                vcedit_clear_internals (state);
-               free (state);
-       }
+
+       vcedit_state_free (state);
 }
 
 /* Next two functions pulled straight from libvorbis, apart from one change
@@ -168,6 +162,8 @@ _v_writestring (oggpack_buffer *o, char *s, int len)
 static int
 _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
 {
+       int i;
+
        oggpack_buffer opb;
 
        oggpack_writeinit (&opb);
@@ -183,16 +179,13 @@ _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
        /* comments */
        oggpack_write (&opb, vc->comments, 32);
 
-       if (vc->comments) {
-               int i;
-
-               for (i = 0; i < vc->comments; i++) {
-                       if (vc->user_comments[i]) {
-                               oggpack_write (&opb, vc->comment_lengths[i], 32);
-                               _v_writestring (&opb, vc->user_comments[i],
-                                               vc->comment_lengths[i]);
-                       } else
-                               oggpack_write (&opb, 0, 32);
+       for (i = 0; i < vc->comments; i++) {
+               if (!vc->user_comments[i])
+                       oggpack_write (&opb, 0, 32);
+               else {
+                       oggpack_write (&opb, vc->comment_lengths[i], 32);
+                       _v_writestring (&opb, vc->user_comments[i],
+                                       vc->comment_lengths[i]);
                }
        }
 
@@ -214,13 +207,12 @@ _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
 static int
 _blocksize (vcedit_state *s, ogg_packet *p)
 {
-       int this = vorbis_packet_blocksize (s->vi, p);
-       int ret = (this + s->prevW) / 4;
+       int this, ret = 0;
 
-       if (!s->prevW) {
-               s->prevW = this;
-               return 0;
-       }
+       this = vorbis_packet_blocksize (&s->vi, p);
+
+       if (s->prevW)
+               ret = (this + s->prevW) / 4;
 
        s->prevW = this;
 
@@ -233,7 +225,7 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
        char *buffer;
        int result, bytes;
 
-       result = ogg_stream_packetout (s->os, p);
+       result = ogg_stream_packetout (&s->os, p);
 
        if (result > 0)
                return 1;
@@ -241,10 +233,10 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
        if (s->eosin)
                return 0;
 
-       while (ogg_sync_pageout (s->oy, page) <= 0) {
-               buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
+       while (ogg_sync_pageout (&s->oy, page) <= 0) {
+               buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
                bytes = fread (buffer, 1, CHUNKSIZE, s->in);
-               ogg_sync_wrote (s->oy, bytes);
+               ogg_sync_wrote (&s->oy, bytes);
 
                if (!bytes)
                        return 0;
@@ -258,14 +250,15 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
                return 0;
        }
 
-       ogg_stream_pagein (s->os, page);
+       ogg_stream_pagein (&s->os, page);
 
        return _fetch_next_packet (s, p, page);
 }
 
-static int
+vcedit_error
 vcedit_open (vcedit_state *state)
 {
+       vcedit_error ret;
        char *buffer;
        int bytes, i;
        int chunks = 0;
@@ -273,52 +266,47 @@ vcedit_open (vcedit_state *state)
        ogg_packet header_main, header_comments, header_codebooks;
        ogg_page og;
 
-       state->oy = malloc (sizeof (ogg_sync_state));
-       ogg_sync_init (state->oy);
+       state->in = fopen (state->filename, "rb");
+       if (!state->in)
+               return VCEDIT_ERR_OPEN;
+
+       ogg_sync_init (&state->oy);
 
        while (1) {
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
+               buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
                bytes = fread (buffer, 1, CHUNKSIZE, state->in);
 
-               ogg_sync_wrote (state->oy, bytes);
+               ogg_sync_wrote (&state->oy, bytes);
 
-               if (ogg_sync_pageout (state->oy, &og) == 1)
+               if (ogg_sync_pageout (&state->oy, &og) == 1)
                        break;
 
                /* Bail if we don't find data in the first 40 kB */
                if (chunks++ >= 10) {
-                       if (bytes < CHUNKSIZE)
-                               state->lasterror = "Input truncated or empty.";
-                       else
-                               state->lasterror = "Input is not an Ogg bitstream.";
+                       ogg_sync_clear (&state->oy);
 
-                       goto err;
+                       return VCEDIT_ERR_INVAL;
                }
        }
 
        state->serial = ogg_page_serialno (&og);
 
-       state->os = malloc (sizeof (ogg_stream_state));
-       ogg_stream_init (state->os, state->serial);
-
-       state->vi = malloc (sizeof (vorbis_info));
-       vorbis_info_init (state->vi);
+       ogg_stream_init (&state->os, state->serial);
+       vorbis_info_init (&state->vi);
+       vorbis_comment_init (&state->vc);
 
-       state->vc = malloc (sizeof (vorbis_comment));
-       vorbis_comment_init (state->vc);
-
-       if (ogg_stream_pagein (state->os, &og) < 0) {
-               state->lasterror = "Error reading first page of Ogg bitstream.";
+       if (ogg_stream_pagein (&state->os, &og) < 0) {
+               ret = VCEDIT_ERR_INVAL;
                goto err;
        }
 
-       if (ogg_stream_packetout (state->os, &header_main) != 1) {
-               state->lasterror = "Error reading initial header packet.";
+       if (ogg_stream_packetout (&state->os, &header_main) != 1) {
+               ret = VCEDIT_ERR_INVAL;
                goto err;
        }
 
-       if (vorbis_synthesis_headerin (state->vi, state->vc, &header_main) < 0) {
-               state->lasterror = "Ogg bitstream does not contain vorbis data.";
+       if (vorbis_synthesis_headerin (&state->vi, &state->vc, &header_main) < 0) {
+               ret = VCEDIT_ERR_INVAL;
                goto err;
        }
 
@@ -331,26 +319,26 @@ vcedit_open (vcedit_state *state)
 
        while (i < 2) {
                while (i < 2) {
-                       int result = ogg_sync_pageout (state->oy, &og);
+                       int result = ogg_sync_pageout (&state->oy, &og);
 
                        if (!result)
                                break; /* Too little data so far */
 
                        if (result == 1) {
-                               ogg_stream_pagein (state->os, &og);
+                               ogg_stream_pagein (&state->os, &og);
 
                                while (i < 2) {
-                                       result = ogg_stream_packetout (state->os, header);
+                                       result = ogg_stream_packetout (&state->os, header);
 
                                        if (!result)
                                                break;
 
                                        if (result == -1) {
-                                               state->lasterror = "Corrupt secondary header.";
+                                               ret = VCEDIT_ERR_INVAL;
                                                goto err;
                                        }
 
-                                       vorbis_synthesis_headerin (state->vi, state->vc, header);
+                                       vorbis_synthesis_headerin (&state->vi, &state->vc, header);
 
                                        if (i == 1) {
                                                state->booklen = header->bytes;
@@ -364,30 +352,32 @@ vcedit_open (vcedit_state *state)
                        }
                }
 
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
+               buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
                bytes = fread (buffer, 1, CHUNKSIZE, state->in);
 
                if (bytes == 0 && i < 2) {
-                       state->lasterror = "EOF before end of vorbis headers.";
+                       ret = VCEDIT_ERR_INVAL;
                        goto err;
                }
 
-               ogg_sync_wrote (state->oy, bytes);
+               ogg_sync_wrote (&state->oy, bytes);
        }
 
        /* Copy the vendor tag */
-       state->vendor = strdup (state->vc->vendor);
+       state->vendor = strdup (state->vc.vendor);
 
        /* Headers are done! */
-       return 0;
+       state->opened = true;
+
+       return VCEDIT_ERR_SUCCESS;
 
 err:
        vcedit_clear_internals (state);
 
-       return -1;
+       return ret;
 }
 
-int
+vcedit_error
 vcedit_write (vcedit_state *state)
 {
        ogg_stream_state streamout;
@@ -399,27 +389,25 @@ vcedit_write (vcedit_state *state)
        int s, result, bytes, needflush = 0, needout = 0;
        size_t tmp;
 
+       if (!state->opened)
+               return VCEDIT_ERR_INVAL;
+
        strcpy (tmpfile, state->filename);
        strcat (tmpfile, ".XXXXXX");
 
        s = mkstemp (tmpfile);
-       if (s == -1) {
-               state->lasterror = "Error writing stream to output. "
-                                  "Cannot open temporary file.";
-               return -1;
-       }
+       if (s == -1)
+               return VCEDIT_ERR_TMPFILE;
 
        out = fdopen (s, "wb");
        if (!out) {
                unlink (tmpfile);
                close (s);
-               state->lasterror = "Error writing stream to output. "
-                                  "Cannot open temporary file.";
-               return -1;
+
+               return VCEDIT_ERR_TMPFILE;
        }
 
-       state->eosin = 0;
-       state->extrapage = 0;
+       state->prevW = state->extrapage = state->eosin = 0;
 
        header_main.bytes = state->mainlen;
        header_main.packet = state->mainbuf;
@@ -435,7 +423,7 @@ vcedit_write (vcedit_state *state)
 
        ogg_stream_init (&streamout, state->serial);
 
-       _commentheader_out (state->vc, state->vendor, &header_comments);
+       _commentheader_out (&state->vc, state->vendor, &header_comments);
 
        ogg_stream_packetin (&streamout, &header_main);
        ogg_stream_packetin (&streamout, &header_comments);
@@ -529,14 +517,12 @@ vcedit_write (vcedit_state *state)
                 * through, a page at a time.
                 */
                while (1) {
-                       result = ogg_sync_pageout (state->oy, &ogout);
+                       result = ogg_sync_pageout (&state->oy, &ogout);
 
                        if (!result)
                 break;
 
-                       if (result < 0)
-                               state->lasterror = "Corrupt or missing data, continuing...";
-                       else {
+                       if (result >= 0) {
                                /* Don't bother going through the rest, we can just
                                 * write the page out now
                                 */
@@ -550,9 +536,9 @@ vcedit_write (vcedit_state *state)
                        }
                }
 
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
+               buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE);
                bytes = fread (buffer, 1, CHUNKSIZE, state->in);
-               ogg_sync_wrote (state->oy, bytes);
+               ogg_sync_wrote (&state->oy, bytes);
 
                if (!bytes) {
                        state->eosin = 1;
@@ -579,16 +565,11 @@ cleanup:
 
     state->mainbuf = state->bookbuf = NULL;
 
-       if (!state->eosin) {
-               state->lasterror = "Error writing stream to output. "
-                                  "Output stream may be corrupted or truncated.";
-               return -1;
-       }
+       if (!state->eosin)
+               return VCEDIT_ERR_INVAL;
 
        vcedit_clear_internals (state);
 
-       state->in = fopen (state->filename, "rb");
-       vcedit_open (state);
-
-       return 0;
+       return (vcedit_open (state) == VCEDIT_ERR_SUCCESS) ?
+              VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;
 }