+#define CBDATA_BUFSIZE 8192
+
+struct cbdata {
+ /* the SVs we use to call back to Perl */
+ SV *writecb;
+ SV *readcb;
+ SV *seekcb;
+ SV *closecb;
+
+ /* we need to remember whether the buffer contains write data or
+ read data
+ */
+ int reading;
+ int writing;
+
+ /* how far we've read into the buffer (not used for writing) */
+ int where;
+
+ /* the amount of space used/data available in the buffer */
+ int used;
+
+ /* the maximum amount to fill the buffer before flushing
+ If any write is larger than this then the buffer is flushed and
+ the full write is performed. The write is _not_ split into
+ maxwrite sized calls
+ */
+ int maxlength;
+
+ char buffer[CBDATA_BUFSIZE];
+};
+
+/*
+
+call_writer(cbd, buf, size)
+
+Low-level function to call the perl writer callback.
+
+*/
+
+static ssize_t call_writer(struct cbdata *cbd, void const *buf, size_t size) {
+ int count;
+ int success;
+ SV *sv;
+ dSP;
+
+ if (!SvOK(cbd->writecb))
+ return -1;
+
+ ENTER;
+ SAVETMPS;
+ EXTEND(SP, 1);
+ PUSHMARK(SP);
+ PUSHs(sv_2mortal(newSVpv((char *)buf, size)));
+ PUTBACK;
+
+ count = perl_call_sv(cbd->writecb, G_SCALAR);
+
+ SPAGAIN;
+ if (count != 1)
+ croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+ sv = POPs;
+ success = SvTRUE(sv);
+
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return success ? size : -1;
+}
+
+static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
+ size_t maxread) {
+ int count;
+ int result;
+ SV *data;
+ dSP;
+
+ if (!SvOK(cbd->readcb))
+ return -1;
+
+ ENTER;
+ SAVETMPS;
+ EXTEND(SP, 2);
+ PUSHMARK(SP);
+ PUSHs(sv_2mortal(newSViv(size)));
+ PUSHs(sv_2mortal(newSViv(maxread)));
+ PUTBACK;
+
+ count = perl_call_sv(cbd->readcb, G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1)
+ croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+ data = POPs;
+
+ if (SvOK(data)) {
+ STRLEN len;
+ char *ptr = SvPV(data, len);
+ if (len > maxread)
+ croak("Too much data returned in reader callback");
+
+ memcpy(buf, ptr, len);
+ result = len;
+ }
+ else {
+ result = -1;
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return result;
+}
+
+static ssize_t write_flush(struct cbdata *cbd) {
+ ssize_t result;
+
+ if (cbd->used) {
+ result = call_writer(cbd, cbd->buffer, cbd->used);
+ cbd->used = 0;
+ return result;
+ }
+ else {
+ return 1; /* success of some sort */
+ }
+}
+
+static off_t io_seeker(void *p, off_t offset, int whence) {
+ struct cbdata *cbd = p;
+ int count;
+ off_t result;
+ dSP;
+
+ if (!SvOK(cbd->seekcb))
+ return -1;
+
+ if (cbd->writing) {
+ if (cbd->used && write_flush(cbd) <= 0)
+ return -1;
+ cbd->writing = 0;
+ }
+ if (whence == SEEK_CUR && cbd->reading && cbd->where != cbd->used) {
+ offset -= cbd->where - cbd->used;
+ }
+ cbd->reading = 0;
+ cbd->where = cbd->used = 0;
+
+ ENTER;
+ SAVETMPS;
+ EXTEND(SP, 2);
+ PUSHMARK(SP);
+ PUSHs(sv_2mortal(newSViv(offset)));
+ PUSHs(sv_2mortal(newSViv(whence)));
+ PUTBACK;
+
+ count = perl_call_sv(cbd->seekcb, G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1)
+ croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+ result = POPi;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return result;
+}
+
+static ssize_t io_writer(void *p, void const *data, size_t size) {
+ struct cbdata *cbd = p;
+
+ /* printf("io_writer(%p, %p, %u)\n", p, data, size); */
+ if (!cbd->writing) {
+ if (cbd->reading && cbd->where < cbd->used) {
+ /* we read past the place where the caller expected us to be
+ so adjust our position a bit */
+ if (io_seeker(p, cbd->where - cbd->used, SEEK_CUR) < 0) {
+ return -1;
+ }
+ cbd->reading = 0;
+ }
+ cbd->where = cbd->used = 0;
+ }
+ cbd->writing = 1;
+ if (cbd->used && cbd->used + size > cbd->maxlength) {
+ int write_res = write_flush(cbd);
+ if (write_res <= 0) {
+ return write_res;
+ }
+ cbd->used = 0;
+ }
+ if (cbd->used+size <= cbd->maxlength) {
+ memcpy(cbd->buffer + cbd->used, data, size);
+ cbd->used += size;
+ return size;
+ }
+ /* it doesn't fit - just pass it up */
+ return call_writer(cbd, data, size);
+}
+
+static ssize_t
+io_reader(void *p, void *data, size_t size) {
+ struct cbdata *cbd = p;
+ ssize_t total;
+ char *out = data; /* so we can do pointer arithmetic */
+
+ /* printf("io_reader(%p, %p, %d)\n", p, data, size); */
+ if (cbd->writing) {
+ if (write_flush(cbd) <= 0)
+ return 0;
+ cbd->writing = 0;
+ }
+
+ cbd->reading = 1;
+ if (size <= cbd->used - cbd->where) {
+ /* simplest case */
+ memcpy(data, cbd->buffer+cbd->where, size);
+ cbd->where += size;
+ return size;
+ }
+ total = 0;
+ memcpy(out, cbd->buffer + cbd->where, cbd->used - cbd->where);
+ total += cbd->used - cbd->where;
+ size -= cbd->used - cbd->where;
+ out += cbd->used - cbd->where;
+ if (size < sizeof(cbd->buffer)) {
+ int did_read = 0;
+ int copy_size;
+ while (size
+ && (did_read = call_reader(cbd, cbd->buffer, size,
+ sizeof(cbd->buffer))) > 0) {
+ cbd->where = 0;
+ cbd->used = did_read;
+
+ copy_size = i_min(size, cbd->used);
+ memcpy(out, cbd->buffer, copy_size);
+ cbd->where += copy_size;
+ out += copy_size;
+ total += copy_size;
+ size -= copy_size;
+ }
+ if (did_read < 0)
+ return -1;
+ }
+ else {
+ /* just read the rest - too big for our buffer*/
+ int did_read;
+ while ((did_read = call_reader(cbd, out, size, size)) > 0) {
+ size -= did_read;
+ total += did_read;
+ out += did_read;
+ }
+ if (did_read < 0)
+ return -1;
+ }
+
+ return total;
+}
+
+static int io_closer(void *p) {
+ struct cbdata *cbd = p;
+
+ if (cbd->writing && cbd->used > 0) {
+ if (write_flush(cbd) < 0)
+ return -1;
+ cbd->writing = 0;
+ }
+
+ if (SvOK(cbd->closecb)) {
+ dSP;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ PUTBACK;
+
+ perl_call_sv(cbd->closecb, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+
+ return 0;
+}
+
+static void io_destroyer(void *p) {
+ struct cbdata *cbd = p;
+
+ SvREFCNT_dec(cbd->writecb);
+ SvREFCNT_dec(cbd->readcb);
+ SvREFCNT_dec(cbd->seekcb);
+ SvREFCNT_dec(cbd->closecb);
+ myfree(cbd);
+}
+