Fixed a compiler warning.
[ruby-vorbistagger.git] / ext / vcedit.c
index c7bd8e75becb618d5ace62fd212f33db2001ca0b..32778a3e735b6e1e14cfb1bab060d7bc203953ee 100644 (file)
  */
 
 #include <stdio.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <ogg/ogg.h>
 #include <vorbis/codec.h>
+#include <assert.h>
 
 #include "vcedit.h"
 
 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;
 
-       vcedit_read_func read;
-       vcedit_write_func write;
+       FILE *in;
+       mode_t file_mode;
 
-       void *in;
+       bool opened;
        long serial;
-       unsigned char *mainbuf;
-       unsigned char *bookbuf;
-       int     mainlen;
-       int     booklen;
-       const char *lasterror;
+
+       ogg_packet packet_main;
+       ogg_packet packet_code_books;
+
        char *vendor;
        int prevW;
-       int extrapage;
-       int eosin;
+
+       bool extra_page;
+       bool eos;
+
+       char filename[0];
 };
 
-vcedit_state *
-vcedit_state_new (void)
+static void
+ogg_packet_init (ogg_packet *p, unsigned char *buf, long len)
 {
-       vcedit_state *state;
+       p->packet = buf;
+       p->bytes = len;
 
-       state = malloc (sizeof (vcedit_state));
-       if (!state)
-               return NULL;
+       p->b_o_s = p->e_o_s = 0;
+       p->granulepos = p->packetno = 0;
+}
+
+static bool
+ogg_packet_dup_data (ogg_packet *p)
+{
+       unsigned char *tmp;
 
-       memset (state, 0, sizeof (vcedit_state));
+       tmp = malloc (p->bytes);
+       if (!tmp)
+               return false;
 
-       state->refcount = 1;
+       memcpy (tmp, p->packet, p->bytes);
+       p->packet = tmp;
 
-       return state;
+       return true;
 }
 
-const char *
-vcedit_error (vcedit_state *state)
+static void
+vcedit_state_free (vcedit_state *s)
 {
-       return state->lasterror;
+       free (s->vendor);
+
+       if (s->in) {
+               fclose (s->in);
+               s->in = NULL;
+       }
+
+       free (s);
 }
 
-vorbis_comment *
-vcedit_comments (vcedit_state *state)
+static bool
+vcedit_state_init (vcedit_state *s, const char *filename)
 {
-       return state->vc;
+       s->refcount = 1;
+
+       ogg_packet_init (&s->packet_main, NULL, 0);
+       ogg_packet_init (&s->packet_code_books, NULL, 0);
+
+       strcpy (s->filename, filename);
+
+       return true;
 }
 
-static void
-vcedit_clear_internals (vcedit_state *state)
+vcedit_state *
+vcedit_state_new (const char *filename)
 {
-       const char *tmp;
+       vcedit_state *s;
+       size_t len;
 
-       if (state->vc) {
-               vorbis_comment_clear (state->vc);
-               free (state->vc);
-       }
+       len = strlen (filename);
+       if (len > PATH_MAX)
+               return NULL;
 
-       if (state->os) {
-               ogg_stream_clear (state->os);
-               free (state->os);
-       }
+       s= malloc (sizeof (vcedit_state) + len + 1);
+       if (!s)
+               return NULL;
 
-       if (state->oy) {
-               ogg_sync_clear (state->oy);
-               free (state->oy);
+       memset (s, 0, sizeof (vcedit_state));
+
+       if (!vcedit_state_init (s, filename)) {
+               vcedit_state_free (s);
+               return NULL;
        }
 
-       free (state->vendor);
-       free (state->mainbuf);
-       free (state->bookbuf);
+       return s;
+}
+
+vorbis_comment *
+vcedit_comments (vcedit_state *s)
+{
+       return s->opened ? &s->vc : NULL;
+}
+
+static void
+vcedit_clear_internals (vcedit_state *s)
+{
+       ogg_stream_clear (&s->os);
+       ogg_sync_clear (&s->oy);
+
+       vorbis_info_clear (&s->vi);
+       vorbis_comment_clear (&s->vc);
+
+       free (s->vendor);
+       s->vendor = NULL;
 
-    if (state->vi) {
-               vorbis_info_clear (state->vi);
-        free (state->vi);
-    }
+       ogg_packet_clear (&s->packet_main);
+       ogg_packet_clear (&s->packet_code_books);
 
-    tmp = state->lasterror;
-    memset (state, 0, sizeof (vcedit_state));
-    state->lasterror = tmp;
+       s->serial = 0;
+       s->opened = false;
 }
 
 void
-vcedit_state_ref (vcedit_state *state)
+vcedit_state_ref (vcedit_state *s)
 {
-       state->refcount++;
+       s->refcount++;
 }
 
 void
-vcedit_state_unref (vcedit_state *state)
+vcedit_state_unref (vcedit_state *s)
 {
-       state->refcount--;
+       if (--s->refcount)
+               return;
 
-       if (!state->refcount) {
-               vcedit_clear_internals (state);
-               free (state);
-       }
+       if (s->opened)
+               vcedit_clear_internals (s);
+
+       vcedit_state_free (s);
 }
 
 /* Next two functions pulled straight from libvorbis, apart from one change
  * - we don't want to overwrite the vendor string.
  */
 static void
-_v_writestring (oggpack_buffer *o, char *s, int len)
+_v_writestring (oggpack_buffer *o, const char *s, int len)
 {
-       while (len--) {
+       while (len--)
                oggpack_write (o, *s++, 8);
-       }
 }
 
-static int
-_commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
+static bool
+write_comments (vcedit_state *s, ogg_packet *packet)
 {
        oggpack_buffer opb;
+       size_t len;
+       int i;
+
+       ogg_packet_init (packet, NULL, 0);
 
        oggpack_writeinit (&opb);
 
@@ -154,50 +204,47 @@ _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op)
        _v_writestring (&opb, "vorbis", 6);
 
        /* vendor */
-       oggpack_write (&opb, strlen (vendor), 32);
-       _v_writestring (&opb, vendor, strlen (vendor));
+       len = strlen (s->vendor);
+       oggpack_write (&opb, len, 32);
+       _v_writestring (&opb, s->vendor, len);
 
        /* 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);
+       oggpack_write (&opb, s->vc.comments, 32);
+
+       for (i = 0; i < s->vc.comments; i++) {
+               if (!s->vc.user_comments[i])
+                       oggpack_write (&opb, 0, 32);
+               else {
+                       oggpack_write (&opb, s->vc.comment_lengths[i], 32);
+                       _v_writestring (&opb, s->vc.user_comments[i],
+                                       s->vc.comment_lengths[i]);
                }
        }
 
        oggpack_write (&opb, 1, 1);
 
-       op->packet = _ogg_malloc (oggpack_bytes (&opb));
-       memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
+       packet->bytes = oggpack_bytes (&opb);
 
-       op->bytes = oggpack_bytes (&opb);
-       op->b_o_s = 0;
-       op->e_o_s = 0;
-       op->granulepos = 0;
+       packet->packet = _ogg_malloc (packet->bytes);
+       if (!packet->packet)
+               return false;
+
+       memcpy (packet->packet, opb.buffer, packet->bytes);
 
        oggpack_writeclear (&opb);
 
-       return 0;
+       return true;
 }
 
 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;
 
@@ -210,337 +257,333 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
        char *buffer;
        int result, bytes;
 
-       result = ogg_stream_packetout (s->os, p);
-
-       if (result > 0)
+       result = ogg_stream_packetout (&s->os, p);
+       if (result == 1)
                return 1;
 
-       if (s->eosin)
+       if (s->eos)
                return 0;
 
-       while (ogg_sync_pageout (s->oy, page) <= 0) {
-               buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
-               bytes = s->read (buffer, 1, CHUNKSIZE, s->in);
-               ogg_sync_wrote (s->oy, bytes);
+       while (ogg_sync_pageout (&s->oy, page) != 1) {
+               buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+               bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+               ogg_sync_wrote (&s->oy, bytes);
 
-               if (!bytes)
+               if (!bytes && (feof (s->in) || ferror (s->in)))
                        return 0;
        }
 
        if (ogg_page_eos (page))
-               s->eosin = 1;
+               s->eos = true;
        else if (ogg_page_serialno (page) != s->serial) {
-               s->eosin = 1;
-               s->extrapage = 1;
+               s->eos = true;
+               s->extra_page = true;
+
                return 0;
        }
 
-       ogg_stream_pagein (s->os, page);
+       ogg_stream_pagein (&s->os, page);
 
        return _fetch_next_packet (s, p, page);
 }
 
-int
-vcedit_open_callbacks (vcedit_state *state, void *in,
-                       vcedit_read_func read_func,
-                       vcedit_write_func write_func)
+vcedit_error
+vcedit_open (vcedit_state *s)
 {
+       vcedit_error ret;
+       ogg_packet packet_comments, *header = &packet_comments;
+       ogg_page page;
+       struct stat st;
        char *buffer;
-       int bytes, i;
-       int chunks = 0;
-       ogg_packet *header;
-       ogg_packet header_main, header_comments, header_codebooks;
-       ogg_page og;
-
-       state->in = in;
-       state->read = read_func;
-       state->write = write_func;
+       size_t bytes, total = 0;
+       int i = 0;
 
-       state->oy = malloc (sizeof (ogg_sync_state));
-       ogg_sync_init (state->oy);
+       s->in = fopen (s->filename, "rb");
+       if (!s->in)
+               return VCEDIT_ERR_OPEN;
 
-       while (1) {
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
+       s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode;
 
-               ogg_sync_wrote (state->oy, bytes);
-
-               if (ogg_sync_pageout (state->oy, &og) == 1)
-                       break;
+       ogg_sync_init (&s->oy);
 
+       do {
                /* 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.";
+               if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
+                       ogg_sync_clear (&s->oy);
 
-                       goto err;
+                       return VCEDIT_ERR_INVAL;
                }
-       }
 
-       state->serial = ogg_page_serialno (&og);
+               buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
 
-       state->os = malloc (sizeof (ogg_stream_state));
-       ogg_stream_init (state->os, state->serial);
+               bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+               total += bytes;
 
-       state->vi = malloc (sizeof (vorbis_info));
-       vorbis_info_init (state->vi);
+               ogg_sync_wrote (&s->oy, bytes);
+       } while (ogg_sync_pageout (&s->oy, &page) != 1);
 
-       state->vc = malloc (sizeof (vorbis_comment));
-       vorbis_comment_init (state->vc);
+       s->serial = ogg_page_serialno (&page);
 
-       if (ogg_stream_pagein (state->os, &og) < 0) {
-               state->lasterror = "Error reading first page of Ogg bitstream.";
+       ogg_stream_init (&s->os, s->serial);
+       vorbis_info_init (&s->vi);
+       vorbis_comment_init (&s->vc);
+
+       if (ogg_stream_pagein (&s->os, &page) < 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 (&s->os, &s->packet_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 (!ogg_packet_dup_data (&s->packet_main)) {
+               s->packet_main.packet = NULL;
+               ret = VCEDIT_ERR_INVAL;
                goto err;
        }
 
-       state->mainlen = header_main.bytes;
-       state->mainbuf = malloc (state->mainlen);
-       memcpy (state->mainbuf, header_main.packet, header_main.bytes);
+       if (vorbis_synthesis_headerin (&s->vi, &s->vc, &s->packet_main) < 0) {
+               ret = VCEDIT_ERR_INVAL;
+               goto err;
+       }
 
-       i = 0;
-       header = &header_comments;
+       ogg_packet_init (&packet_comments, NULL, 0);
 
        while (i < 2) {
+               if (feof (s->in) || ferror (s->in)) {
+                       ret = VCEDIT_ERR_INVAL;
+                       goto err;
+               }
+
                while (i < 2) {
-                       int result = ogg_sync_pageout (state->oy, &og);
+                       int result;
 
+                       result = ogg_sync_pageout (&s->oy, &page);
                        if (!result)
                                break; /* Too little data so far */
 
-                       if (result == 1) {
-                               ogg_stream_pagein (state->os, &og);
+                       if (result != 1)
+                               continue;
 
-                               while (i < 2) {
-                                       result = ogg_stream_packetout (state->os, header);
+                       ogg_stream_pagein (&s->os, &page);
 
-                                       if (!result)
-                                               break;
+                       while (i < 2) {
+                               result = ogg_stream_packetout (&s->os, header);
+                               if (!result)
+                                       break;
 
-                                       if (result == -1) {
-                                               state->lasterror = "Corrupt secondary header.";
-                                               goto err;
-                                       }
+                               if (result != 1) {
+                                       ret = VCEDIT_ERR_INVAL;
+                                       goto err;
+                               }
 
-                                       vorbis_synthesis_headerin (state->vi, state->vc, header);
+                               if (i++ == 1 && !ogg_packet_dup_data (header)) {
+                                       header->packet = NULL;
+                                       ret = VCEDIT_ERR_INVAL;
+                                       goto err;
+                               }
 
-                                       if (i == 1) {
-                                               state->booklen = header->bytes;
-                                               state->bookbuf = malloc (state->booklen);
-                                               memcpy (state->bookbuf, header->packet, header->bytes);
-                                       }
+                               vorbis_synthesis_headerin (&s->vi, &s->vc, header);
 
-                                       i++;
-                                       header = &header_codebooks;
-                               }
+                               header = &s->packet_code_books;
                        }
                }
 
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
-
-               if (bytes == 0 && i < 2) {
-                       state->lasterror = "EOF before end of vorbis headers.";
-                       goto err;
-               }
+               buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
 
-               ogg_sync_wrote (state->oy, bytes);
+               bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+               ogg_sync_wrote (&s->oy, bytes);
        }
 
        /* Copy the vendor tag */
-       state->vendor = strdup (state->vc->vendor);
+       s->vendor = strdup (s->vc.vendor);
 
        /* Headers are done! */
-       return 0;
+       s->opened = true;
+
+       return VCEDIT_ERR_SUCCESS;
 
 err:
-       vcedit_clear_internals (state);
+       vcedit_clear_internals (s);
 
-       return -1;
+       return ret;
 }
 
-int
-vcedit_write (vcedit_state *state, void *out)
+static bool
+write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
 {
-       ogg_stream_state streamout;
-       ogg_packet header_main, header_comments, header_codebooks, op;
-       ogg_page ogout, ogin;
+       while (nmemb > 0) {
+               size_t w;
+
+               w = fwrite (buf, size, nmemb, stream);
+               if (!w && ferror (stream))
+                       return false;
+
+               nmemb -= w;
+               buf += size * w;
+       }
+
+       return true;
+}
+
+static bool
+write_page (FILE *f, ogg_page *p)
+{
+       return write_data (p->header, 1, p->header_len, f) &&
+              write_data (p->body, 1, p->body_len, f);
+}
+
+vcedit_error
+vcedit_write (vcedit_state *s)
+{
+       ogg_stream_state stream;
+       ogg_packet packet;
+       ogg_page page_out, page_in;
        ogg_int64_t granpos = 0;
-       int result, bytes, needflush = 0, needout = 0;
-       char *buffer;
-       size_t tmp;
+       FILE *out;
+       char *buffer, tmpfile[PATH_MAX];
+       bool success = false, need_flush = false, need_out = false;
+       int fd, result, bytes;
 
-       state->eosin = 0;
-       state->extrapage = 0;
+       if (!s->opened)
+               return VCEDIT_ERR_INVAL;
 
-       header_main.bytes = state->mainlen;
-       header_main.packet = state->mainbuf;
-       header_main.b_o_s = 1;
-       header_main.e_o_s = 0;
-       header_main.granulepos = 0;
+       strcpy (tmpfile, s->filename);
+       strcat (tmpfile, ".XXXXXX");
 
-       header_codebooks.bytes = state->booklen;
-       header_codebooks.packet = state->bookbuf;
-       header_codebooks.b_o_s = 0;
-       header_codebooks.e_o_s = 0;
-       header_codebooks.granulepos = 0;
+       fd = mkstemp (tmpfile);
+       if (fd == -1)
+               return VCEDIT_ERR_TMPFILE;
 
-       ogg_stream_init (&streamout, state->serial);
+       out = fdopen (fd, "wb");
+       if (!out) {
+               unlink (tmpfile);
+               close (fd);
 
-       _commentheader_out (state->vc, state->vendor, &header_comments);
+               return VCEDIT_ERR_TMPFILE;
+       }
 
-       ogg_stream_packetin (&streamout, &header_main);
-       ogg_stream_packetin (&streamout, &header_comments);
-       ogg_stream_packetin (&streamout, &header_codebooks);
+       s->prevW = 0;
+       s->extra_page = s->eos = false;
 
-       while ((result = ogg_stream_flush (&streamout, &ogout))) {
-               tmp = state->write (ogout.header, 1, ogout.header_len, out);
-               if (tmp != (size_t) ogout.header_len)
-                       goto cleanup;
+       ogg_stream_init (&stream, s->serial);
 
-               tmp = state->write (ogout.body, 1, ogout.body_len, out);
-               if (tmp != (size_t) ogout.body_len)
-                       goto cleanup;
+       /* write "main" packet */
+       s->packet_main.b_o_s = 1;
+       ogg_stream_packetin (&stream, &s->packet_main);
+       s->packet_main.b_o_s = 0;
+
+       /* prepare and write comments */
+       if (!write_comments (s, &packet)) {
+               ogg_stream_clear (&stream);
+               unlink (tmpfile);
+               fclose (out);
+
+               return VCEDIT_ERR_INVAL;
        }
 
-       while (_fetch_next_packet (state, &op, &ogin)) {
+       ogg_stream_packetin (&stream, &packet);
+       ogg_packet_clear (&packet);
+
+       /* write codebooks */
+       ogg_stream_packetin (&stream, &s->packet_code_books);
+
+       while (ogg_stream_flush (&stream, &page_out))
+               if (!write_page (out, &page_out))
+                       goto cleanup;
+
+       while (_fetch_next_packet (s, &packet, &page_in)) {
+               bool write = false;
                int size;
 
-               size = _blocksize (state, &op);
+               size = _blocksize (s, &packet);
                granpos += size;
 
-               if (needflush) {
-                       if (ogg_stream_flush (&streamout, &ogout)) {
-                               tmp = state->write (ogout.header, 1, ogout.header_len, out);
-                               if (tmp != (size_t) ogout.header_len)
-                                       goto cleanup;
-
-                               tmp = state->write (ogout.body, 1, ogout.body_len, out);
-                               if (tmp != (size_t) ogout.body_len)
-                                       goto cleanup;
-                       }
-               } else if (needout) {
-                       if (ogg_stream_pageout (&streamout, &ogout)) {
-                               tmp = state->write (ogout.header, 1, ogout.header_len, out);
-                               if (tmp != (size_t) ogout.header_len)
-                                       goto cleanup;
-
-                               tmp = state->write (ogout.body, 1, ogout.body_len, out);
-                               if (tmp != (size_t) ogout.body_len)
-                                       goto cleanup;
-                       }
+               if (need_flush) {
+                       write = ogg_stream_flush (&stream, &page_out);
+               } else if (need_out) {
+                       write = ogg_stream_pageout (&stream, &page_out);
                }
 
-               needflush = needout = 0;
+               if (write && !write_page (out, &page_out))
+                       goto cleanup;
+
+               need_flush = need_out = false;
 
-               if (op.granulepos == -1) {
-                       op.granulepos = granpos;
-                       ogg_stream_packetin (&streamout, &op);
+               if (packet.granulepos == -1) {
+                       packet.granulepos = granpos;
+                       ogg_stream_packetin (&stream, &packet);
                } else {
                        /* granulepos is set, validly. Use it, and force a flush to
                         * account for shortened blocks (vcut) when appropriate
                         */
-                       if (granpos > op.granulepos) {
-                               granpos = op.granulepos;
-                               ogg_stream_packetin (&streamout, &op);
-                               needflush = 1;
+                       if (granpos > packet.granulepos) {
+                               granpos = packet.granulepos;
+                               ogg_stream_packetin (&stream, &packet);
+                               need_flush = true;
                        } else {
-                               ogg_stream_packetin (&streamout, &op);
-                               needout = 1;
+                               ogg_stream_packetin (&stream, &packet);
+                               need_out = true;
                        }
                }
        }
 
-       streamout.e_o_s = 1;
+       stream.e_o_s = 1;
 
-       while (ogg_stream_flush (&streamout, &ogout)) {
-               tmp = state->write (ogout.header, 1, ogout.header_len, out);
-               if (tmp != (size_t) ogout.header_len)
+       while (ogg_stream_flush (&stream, &page_out))
+               if (!write_page (out, &page_out))
                        goto cleanup;
 
-               tmp = state->write (ogout.body, 1, ogout.body_len, out);
-               if (tmp != (size_t) ogout.body_len)
-                       goto cleanup;
-       }
-
-       if (state->extrapage) {
-               tmp = state->write (ogin.header, 1, ogin.header_len, out);
-               if (tmp != (size_t) ogin.header_len)
-                       goto cleanup;
-
-               tmp = state->write (ogin.body, 1, ogin.body_len, out);
-               if (tmp != (size_t) ogin.body_len)
-                       goto cleanup;
-       }
+       if (s->extra_page && !write_page (out, &page_in))
+               goto cleanup;
 
        /* clear it, because not all paths to here do */
-       state->eosin = 0;
+       s->eos = false;
 
-       while (!state->eosin) { /* We reached eos, not eof */
+       do {
                /* We copy the rest of the stream (other logical streams)
                 * through, a page at a time.
                 */
-               while (1) {
-                       result = ogg_sync_pageout (state->oy, &ogout);
-
-                       if (!result)
-                break;
-
-                       if (result < 0)
-                               state->lasterror = "Corrupt or missing data, continuing...";
-                       else {
-                               /* Don't bother going through the rest, we can just
-                                * write the page out now
-                                */
-                               tmp = state->write (ogout.header,1,ogout.header_len, out);
-                               if (tmp != (size_t) ogout.header_len)
-                                       goto cleanup;
-
-                               tmp = state->write (ogout.body,1,ogout.body_len, out);
-                               if (tmp != (size_t) ogout.body_len)
-                                       goto cleanup;
-                       }
+               while ((result = ogg_sync_pageout (&s->oy, &page_out))) {
+                       /* Don't bother going through the rest, we can just
+                        * write the page out now
+                        */
+                       if (result == 1 && !write_page (out, &page_out))
+                               goto cleanup;
                }
 
-               buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
-               ogg_sync_wrote (state->oy, bytes);
+               buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+               bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+               ogg_sync_wrote (&s->oy, bytes);
 
-               if (!bytes) {
-                       state->eosin = 1;
-                       break;
-               }
-       }
+               if (ferror (s->in))
+                       goto cleanup;
+       } while (bytes || !feof (s->in));
 
-cleanup:
-       ogg_stream_clear (&streamout);
+       s->eos = success = true;
 
-    /* We don't ogg_packet_clear() this, because the memory was
-        * allocated in _commentheader_out(), so we mirror that here
-        */
-    _ogg_free (header_comments.packet);
+cleanup:
+       fclose (s->in);
+
+       if (!success) {
+               unlink (tmpfile);
+               fclose (out);
+       } else {
+               fclose (out);
+               unlink (s->filename);
+               rename (tmpfile, s->filename);
+               chmod (s->filename, s->file_mode);
+       }
 
-       free (state->mainbuf);
-       free (state->bookbuf);
+       ogg_stream_clear (&stream);
 
-    state->mainbuf = state->bookbuf = NULL;
+       if (!s->eos)
+               return VCEDIT_ERR_INVAL;
 
-       if (!state->eosin) {
-               state->lasterror = "Error writing stream to output. "
-                                  "Output stream may be corrupted or truncated.";
-               return -1;
-       }
+       vcedit_clear_internals (s);
 
-       return 0;
+       return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
+              VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;
 }