The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include <linux/cdrom.h>

#include "const-c.inc"

#define WARN_OFF \
    SV *oldwarn = PL_curcop->cop_warnings; \
    PL_curcop->cop_warnings = pWARN_NONE;

#define WARN_ON \
    PL_curcop->cop_warnings = oldwarn;

#define DEVICE_CDROM_NO_ERROR           0
#define DEVICE_CDROM_NO_OPEN            1
#define DEVICE_CDROM_NO_CDROM           2
#define DEVICE_CDROM_NO_TOCHDR          3
#define DEVICE_CDROM_NO_AUDIO           4
#define DEVICE_CDROM_NO_DISC_STATUS     5
#define DEVICE_CDROM_IDX_OUT_OF_BOUNDS  6
#define DEVICE_CDROM_IOCTL_ERROR        7

typedef struct CDROM {
    int                 fd;
    char                *device;    /* need device name for reopening it */
    int                 caps;       /* capabilities as returned by CDROM_GET_CAPABILITY */
    struct cdrom_tochdr *toch;   
    int                 num_frames;
} CDROM;

typedef struct CDROM_ADDR {
    union cdrom_addr addr;
    int type;
} CDROM_ADDR;

typedef struct cdrom_subchnl CDROM_SUBCHANNEL;

typedef struct cdrom_tocentry CDROM_TOCENTRY;

/* these two are derived from the kernel's drivers/cdrom/cdrom.c */
int msf_to_lba (char m, char s, char f) {
    return (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_MSF_OFFSET;
}
void lba_to_msf (int lba, char *m, char *s, char *f) {
    *f = lba % CD_FRAMES;
    lba /= CD_FRAMES;
    lba += 2;
    *s = lba % CD_SECS;
    *m = lba / CD_SECS;
}

int reopen (CDROM *self) {
    close(self->fd);
    if ((self->fd = open(self->device, O_RDONLY|O_NONBLOCK)) == -1)
        return 0;
    return 1;
}

void to_lba (CDROM_ADDR *self) {
    if (self->type == CDROM_LBA)
        return;
    self->addr.lba = msf_to_lba(self->addr.msf.minute, self->addr.msf.second, self->addr.msf.frame);
    self->type = CDROM_LBA;
}

int
num_frames (CDROM *self) {
    long num;
    if (ioctl(self->fd, CDROM_LAST_WRITTEN, &num) == -1)
        return -1;
    return (self->num_frames = num);
}

/* $Linux::CDROM::error handling */
SV *CDERR;
void reg_error (int error) {
    STRLEN n_a;
    CDERR = get_sv("Linux::CDROM::error", FALSE);
    SvIVX(CDERR) = error;
    switch (error) {
        case DEVICE_CDROM_NO_ERROR:
            sv_setpvn(CDERR, "", 0);
            break;
        case DEVICE_CDROM_NO_OPEN:
            sv_setpvn(CDERR, "Couldn't open device: ", 22);
        case DEVICE_CDROM_NO_CDROM:
            sv_setpvn(CDERR, "Device is no CDROM drive: ", 26);
        case DEVICE_CDROM_NO_TOCHDR:
            sv_setpvn(CDERR, "Couldn't read TOC header: ", 26);
        case DEVICE_CDROM_NO_AUDIO:
            sv_setpvn(CDERR, "No Audio-CD: ", 13);
        case DEVICE_CDROM_NO_DISC_STATUS:
            sv_setpvn(CDERR, "Couldn't retrieve disc-status: ", 31);
        case DEVICE_CDROM_IDX_OUT_OF_BOUNDS:
            sv_setpvn(CDERR, "Index out of bounds: ", 21);
        case DEVICE_CDROM_IOCTL_ERROR:
            sv_setpvn(CDERR, "Generic ioctl error: ", 21);
        default: 
            sv_catpv(CDERR, SvPV(get_sv("!", FALSE), n_a));
    }
}

SV *DATSIZE;

#define INC_DATSIZE(i)      SvIVX(DATSIZE) += i
#define RESET_DATSIZE       SvIVX(DATSIZE) = 0

MODULE = Linux::CDROM		PACKAGE = Linux::CDROM

INCLUDE: const-xs.inc

PROTOTYPES: DISABLE

BOOT:
{
    CDERR = get_sv("Linux::CDROM::error", TRUE);
    SvUPGRADE(CDERR, SVt_PVIV);
    reg_error(DEVICE_CDROM_NO_ERROR);
    SvIOK_on(CDERR);
    DATSIZE = NEWSV(0,0);
    SvUPGRADE(DATSIZE, SVt_IV);
    SvIOK_on(DATSIZE);
    SvREADONLY_on(DATSIZE);

    /* overload stuff */
    PL_amagic_generation++;
    newXS("Linux::CDROM::Addr::()", XS_Linux__CDROM__Addr_noop, file);
    newXS("Linux::CDROM::Addr::(+", XS_Linux__CDROM__Addr_add, file);
    newXS("Linux::CDROM::Addr::(-", XS_Linux__CDROM__Addr_sub, file);
}

void
reset_datasize (...) 
    CODE:
    {
        RESET_DATSIZE;
    }

int
get_datasize (...)
    CODE:
    {
        RETVAL = SvIVX(DATSIZE);
    }
    OUTPUT:
        RETVAL

CDROM*
new (CLASS, device)
        char *CLASS;
        char *device;
    PREINIT:
        CDROM *cdrom;
        int fd;
        int caps;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
        if ((fd = open(device, O_RDONLY|O_NONBLOCK)) == -1) {
            reg_error(DEVICE_CDROM_NO_OPEN);
            XSRETURN_UNDEF;
        }
        if ((caps = ioctl(fd, CDROM_GET_CAPABILITY)) == -1) {
            reg_error(DEVICE_CDROM_NO_CDROM);
            close(fd);
            XSRETURN_UNDEF;
        } 
            
        New(0, cdrom, 1, CDROM);
        cdrom->fd = fd;
        cdrom->device = savepv(device);
        cdrom->caps = caps;
        cdrom->toch = NULL;
        cdrom->num_frames = -1;
        RETVAL = cdrom;
    }
    OUTPUT:
        RETVAL

void
fh (self)
        CDROM *self;
    PREINIT:
        char mode[8];
        GV *gv;
        STRLEN modlen;
    CODE:
    {
        modlen = sprintf(mode, "<&%i", self->fd);
        gv = newGVgen("main");
        sv_dump((SV*)gv);
        do_openn(gv, mode, modlen, FALSE, O_RDONLY|O_NONBLOCK, 0, Nullfp, (SV**)NULL, 0);
        sv_setsv(ST(0), sv_2mortal(newRV_noinc((SV*)gv)));
        XSRETURN(1);
    }

void
close (self)
        CDROM *self;
    CODE:
    {
        if (close(self->fd) == -1)
            XSRETURN_UNDEF;
        self->fd = -1;
        XSRETURN_YES;
    }
       
void
reopen (self)
        CDROM *self;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
        if (reopen(self)) 
            XSRETURN_YES;
        reg_error(DEVICE_CDROM_NO_OPEN);
        XSRETURN_UNDEF;
    }

int
capabilities (self)
        CDROM *self;
    CODE:
    {
        if (self->caps == -2) {
            self->caps = ioctl(self->fd, CDROM_GET_CAPABILITY);
            if (self->caps == -1)
                XSRETURN_UNDEF;
        }
        RETVAL = self->caps;
    }
    OUTPUT:
        RETVAL

int
drive_status (self)
        CDROM *self;
    PREINIT:
        int status;
    CODE:
    {
        if ((status = ioctl(self->fd, CDROM_DRIVE_STATUS)) == -1)
            XSRETURN_UNDEF;
        RETVAL = status;
    }
    OUTPUT:
        RETVAL
      
UV
disc_status (self)
        CDROM *self;
    PREINIT:
        int status;
    CODE:
    {
        if ((status = ioctl(self->fd, CDROM_DISC_STATUS)) == -1)
            XSRETURN_UNDEF;
        RETVAL = status;
    }
    OUTPUT:
        RETVAL

NV
num_frames (self)
        CDROM *self;
    PREINIT:
        long last;
    CODE:
    {
        if (self->num_frames == -1)
            if (ioctl(self->fd, CDROM_LAST_WRITTEN, &last) == -1)
                XSRETURN_UNDEF;
        RETVAL = last;
    }
    OUTPUT:
        RETVAL

NV
next_writable (self)
        CDROM *self;
    PREINIT:
        long next;
    CODE:
    {
        if (ioctl(self->fd, CDROM_NEXT_WRITABLE, &next) == -1)
            XSRETURN_UNDEF;
        RETVAL = next;
    }
    OUTPUT:
        RETVAL

int
get_spindown (self)
        CDROM *self;
    PREINIT:
        int sd;
    CODE:
    {
        if (ioctl(self->fd, CDROMGETSPINDOWN, &sd) == -1)
            XSRETURN_UNDEF;
        RETVAL = (char)sd;
    }
    OUTPUT:
        RETVAL

void
set_spindown (self, sd)
        CDROM *self;
        int sd;
    CODE:
    {
        if (ioctl(self->fd, CDROMSETSPINDOWN, (void*)&sd) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
reset (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMRESET) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }
    
void
eject (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMEJECT) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void 
auto_eject (self, val)
        CDROM *self;
        int val;
    CODE:
    {
        if (ioctl(self->fd, CDROMEJECT_SW, val) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void 
close_tray (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMCLOSETRAY, 0) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
lock_door (self, val)
        CDROM *self;
        int val;
    CODE:
    {
        if (ioctl(self->fd, CDROM_LOCKDOOR, val) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
media_changed (self)
        CDROM *self;
    PREINIT:
        int changed;
    CODE:
    {
        if ((changed = ioctl(self->fd, CDROM_MEDIA_CHANGED)) == -1)
            XSRETURN_UNDEF;
        if (changed)
            XSRETURN_YES;
        else
            XSRETURN_NO;
    }

void
mcn (self)
        CDROM *self;
    PREINIT:
        struct cdrom_mcn mcn;
    CODE:
    {
        if (ioctl(self->fd, CDROM_GET_MCN, &mcn) == -1)
            XSRETURN_UNDEF;
        ST(0) = sv_2mortal(newSVpvn(mcn.medium_catalog_number, 13));
        XSRETURN(1);
    }


void
get_vol (self)
        CDROM *self;
    PREINIT:
        struct cdrom_volctrl vol;
    CODE:
    {
        if (ioctl(self->fd, CDROMVOLREAD, &vol) == -1)
            XSRETURN_UNDEF;
        EXTEND(SP, 4);
        ST(0) = sv_2mortal(newSVuv(vol.channel0));
        ST(1) = sv_2mortal(newSVuv(vol.channel1));
        ST(2) = sv_2mortal(newSVuv(vol.channel2));
        ST(3) = sv_2mortal(newSVuv(vol.channel3));
        XSRETURN(4);
    }
  
void 
set_vol (self, v0, v1, v2, v3)
        CDROM *self;
        unsigned int v0; 
        unsigned int v1; 
        unsigned int v2;
        unsigned int v3;
    PREINIT:
        struct cdrom_volctrl vol;
    CODE:
    {
        vol.channel0 = v0;
        vol.channel1 = v1;
        vol.channel2 = v2;
        vol.channel3 = v3;
        if (ioctl(self->fd, CDROMVOLCTRL, &vol) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
play_msf (self, from, to) 
        CDROM *self;
        CDROM_ADDR *from;
        CDROM_ADDR *to;
    PREINIT:
        struct cdrom_msf msf;
        int status;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
        
        if ((status = ioctl(self->fd, CDROM_DISC_STATUS)) == -1) {
            reg_error(DEVICE_CDROM_NO_DISC_STATUS);
            XSRETURN_UNDEF;
        }

        if (! status & CDS_AUDIO) {
            reg_error(DEVICE_CDROM_NO_AUDIO);
            XSRETURN_UNDEF;
        }
        
        lba_to_msf(from->addr.lba, &msf.cdmsf_min0, &msf.cdmsf_sec0, &msf.cdmsf_frame0);
        lba_to_msf(to->addr.lba, &msf.cdmsf_min1, &msf.cdmsf_sec1, &msf.cdmsf_frame1);
        if (ioctl(self->fd, CDROMPLAYMSF, &msf) == -1) {
            reg_error(DEVICE_CDROM_IOCTL_ERROR);
            XSRETURN_UNDEF;
        }
        XSRETURN_YES;
    }
   
void
play_ti (self, ...)
        CDROM *self;
    PREINIT:
        register int i;
        int fromtr  = 0;
        int fromidx = 0;
        int totr    = 0;
        int toidx   = 0;
        struct cdrom_ti ti;
        int dotochdr = 2;
        int status;
        STRLEN n_a;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
       
        if ((status = ioctl(self->fd, CDROM_DISC_STATUS)) == -1) {
            reg_error(DEVICE_CDROM_NO_DISC_STATUS);
            XSRETURN_UNDEF;
        }

        if (! status & CDS_AUDIO) {
            reg_error(DEVICE_CDROM_NO_AUDIO);
            XSRETURN_UNDEF;
        }
            
        /* temporarily shut up warnings */
        WARN_OFF;
        for (i = 1; i+1 < items; i++) {
            if (strEQ(SvPV(ST(i), n_a), "-from")) {
                i++;
                fromtr = SvIV(ST(i));
                dotochdr--;
                continue;
            } 
            if (strEQ(SvPV(ST(i), n_a), "-to")) {
                i++;
                totr = SvIV(ST(i));
                dotochdr--;
                continue;
            }
            if (strEQ(SvPV(ST(i), n_a),  "-fromidx")) {
                i++;
                fromidx = SvIV(ST(i));
                continue;
            }
            if (strEQ(SvPV(ST(i), n_a), "-toidx")) {
                i++;
                toidx = SvIV(ST(i));
                continue;
            }
        }
        /* warning-prone code done so restore old
         * warnings bit-mask */
        WARN_ON;
        
        if (dotochdr) {
            if (!self->toch) {
                New(0, self->toch, 1, struct cdrom_tochdr);
                if (ioctl(self->fd, CDROMREADTOCHDR, self->toch) == -1) {
                    reg_error(DEVICE_CDROM_NO_TOCHDR);
                    XSRETURN_UNDEF;
                }
                else {
                    fromtr = 1;
                    totr = self->toch->cdth_trk1;
                }
            }
        }
        
        ti.cdti_trk0 = (__u8)fromtr;
        ti.cdti_ind0 = (__u8)fromidx;
        ti.cdti_trk1 = (__u8)totr;
        ti.cdti_ind1 = (__u8)toidx;

        if (ioctl(self->fd, CDROMPLAYTRKIND, &ti) == -1) {
            reg_error(DEVICE_CDROM_IOCTL_ERROR);
            XSRETURN_UNDEF;
        }
        XSRETURN_YES;
    }
      
void
pause (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMPAUSE) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
resume (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMRESUME) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
start (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMSTART) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void 
stop (self)
        CDROM *self;
    CODE:
    {
        if (ioctl(self->fd, CDROMSTOP) == -1)
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
read1 (self, addr)
        CDROM *self;
        CDROM_ADDR *addr;
    PREINIT:
        struct cdrom_msf *data;
    CODE:
    {
        New(0, data, CD_FRAMESIZE, char);

        lba_to_msf(addr->addr.lba, &data->cdmsf_min0, &data->cdmsf_sec0, &data->cdmsf_frame0);

        if (ioctl(self->fd, CDROMREADMODE1, data) == -1) {
            Safefree(data);
            XSRETURN_UNDEF;
        }
        
        ST(0) = sv_newmortal();
        sv_usepvn(ST(0), (char*)data, CD_FRAMESIZE);
        XSRETURN(1);
    }

void
read2 (self, addr)
        CDROM *self;
        CDROM_ADDR *addr;
    PREINIT:
        struct cdrom_msf *data;
    CODE:
    {
        New(0, data, CD_FRAMESIZE_RAW0, char);

        lba_to_msf(addr->addr.lba, &data->cdmsf_min0, &data->cdmsf_sec0, &data->cdmsf_frame0);

        if (ioctl(self->fd, CDROMREADMODE2, data) == -1) {
            Safefree(data);
            XSRETURN_UNDEF;
        }
      
        ST(0) = sv_newmortal();
        sv_usepvn(ST(0), (char*)data, CD_FRAMESIZE_RAW0);
        XSRETURN(1);
    }
    
void
read_audio (self, addr, nframes)
        CDROM *self;
        CDROM_ADDR *addr;
        int nframes;
    PREINIT:
        struct cdrom_read_audio audio;
        int ret;
        int status;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
       
        /* bound checking stuff */
        
        if (self->num_frames == -1)
            status = num_frames(self);

        if (status >= 0) {
            if (addr->addr.lba >= self->num_frames) {
                reg_error(DEVICE_CDROM_IDX_OUT_OF_BOUNDS);
                XSRETURN_UNDEF;
            }
            /* detect reading-beyond-last-frame case and correct
             * the number of frames */
            if (addr->addr.lba + nframes - 1 >= self->num_frames)
                nframes = self->num_frames - addr->addr.lba;
        }
        
        Newz(0, audio.buf, nframes * CD_FRAMESIZE_RAW, __u8);
        audio.addr = addr->addr;
        audio.addr_format = addr->type;
        audio.nframes = nframes;
        if ((ret = ioctl(self->fd, CDROMREADAUDIO, &audio)) == -1) {
            Safefree(audio.buf);
            XSRETURN_UNDEF;
        }

        INC_DATSIZE(CD_FRAMESIZE_RAW * nframes);
        ST(0) = sv_newmortal();
        sv_usepvn(ST(0), audio.buf, nframes*CD_FRAMESIZE_RAW);
        XSRETURN(1);
    }

#if 0
void
read_cooked (self, lba)
        CDROM *self;
        int lba;
    PREINIT:
        struct cdrom_msf *data;
    CODE:
    {
        New(0, (char*)data, CD_FRAMESIZE, char);

        lba_to_msf(lba, &data->cdmsf_min0, &data->cdmsf_sec0, &data->cdmsf_frame0);

        if (ioctl(self->fd, CDROMREADCOOKED, data) == -1) {
            Safefree(data);
            XSRETURN_UNDEF;
        }
      
        ST(0) = sv_newmortal();
        sv_usepvn(ST(0), (char*)data, CD_FRAMESIZE);
        XSRETURN(1);
    }

#endif

void
read_raw (self, addr)
        CDROM *self;
        CDROM_ADDR *addr;
    PREINIT:
        struct cdrom_msf *data;
    CODE:
    {
        New(0, data, CD_FRAMESIZE_RAW, char);

        lba_to_msf(addr->addr.lba, &data->cdmsf_min0, &data->cdmsf_sec0, &data->cdmsf_frame0);

        if (ioctl(self->fd, CDROMREADRAW, data) == -1) {
            Safefree(data);
            XSRETURN_UNDEF;
        }
      
        ST(0) = sv_newmortal();
        sv_usepvn(ST(0), (char*)data, CD_FRAMESIZE_RAW);
        XSRETURN(1);
    }

CDROM_SUBCHANNEL*
poll (self)
        CDROM *self;
    PREINIT:
        CDROM_SUBCHANNEL *subch;
    CODE:
    {
        New(0, subch, 1, CDROM_SUBCHANNEL);
        subch->cdsc_format = CDROM_LBA;
        if (ioctl(self->fd, CDROMSUBCHNL, subch) == -1) {
            Safefree(subch);
            XSRETURN_UNDEF;
        }
        RETVAL = subch;
    }
    OUTPUT:
        RETVAL
   
void
toc (self)
        CDROM *self;
    CODE:
    {
        if (!self->toch) {
            New(0, self->toch, 1, struct cdrom_tochdr);
            if (ioctl(self->fd, CDROMREADTOCHDR, self->toch) == -1) {
                Safefree(self->toch);
                XSRETURN_UNDEF;
            }
        }
        EXTEND(SP, 2);
        ST(0) = sv_2mortal(newSVuv(self->toch->cdth_trk0));
        ST(1) = sv_2mortal(newSVuv(self->toch->cdth_trk1));
        XSRETURN(2);
    }

CDROM_TOCENTRY*
toc_entry (self, idx)
        CDROM *self;
        int idx;
    PREINIT:
        CDROM_TOCENTRY *entry;
    CODE:
    {
        reg_error(DEVICE_CDROM_NO_ERROR);
        if (!self->toch) {
            New(0, self->toch, 1, struct cdrom_tochdr);
            if (ioctl(self->fd, CDROMREADTOCHDR, self->toch) == -1) {
                reg_error(DEVICE_CDROM_NO_TOCHDR);
                Safefree(self->toch);
                XSRETURN_UNDEF;
            }
        }
        
        if (idx < self->toch->cdth_trk0 || idx > self->toch->cdth_trk1 && idx != CDROM_LEADOUT) {
            reg_error(DEVICE_CDROM_IDX_OUT_OF_BOUNDS);
            XSRETURN_UNDEF;
        }

        New(0, entry, 1, CDROM_TOCENTRY);
        entry->cdte_track  = (__u8)idx;
        entry->cdte_format = CDROM_LBA;
        if (ioctl(self->fd, CDROMREADTOCENTRY, entry) == -1) {
            reg_error(DEVICE_CDROM_IOCTL_ERROR);
            Safefree(entry);
            XSRETURN_UNDEF;
        }
        RETVAL = entry;
    }
    OUTPUT:
        RETVAL

void
is_multisession (self)
        CDROM *self;
    PREINIT:
        struct cdrom_multisession ms;
    CODE:
    {
        ms.addr_format = CDROM_LBA;
        if (ioctl(self->fd, CDROMMULTISESSION, &ms) == -1)
            XSRETURN_UNDEF;
        if (ms.xa_flag) 
            XSRETURN_YES;
        else
            XSRETURN_NO;
    }
    
void
ioctl (self, func, arg) 
        CDROM *self;
        int func;
        SV *arg;
    PREINIT:
        unsigned char *data;
        STRLEN dlen, need;
    CODE:
    {
        /* this is derived from Perl's pp_sys.c:pp_ioctl */
        WARN_OFF;
        data = SvPV_force(arg, dlen);
        need = IOCPARM_LEN(func);
        if (dlen < need) {
            data = SvGROW(arg, need+1);
            SvCUR_set(arg, need);
        }
        WARN_ON;
        if (ioctl(self->fd, func, data) == -1) 
            XSRETURN_UNDEF;
        XSRETURN_YES;
    }

void
DESTROY (self)
        CDROM *self;
    CODE:
    {
        close(self->fd);
        Safefree(self);
    }

MODULE = Linux::CDROM      PACKAGE = Linux::CDROM::Addr

void
noop (...)
    CODE:
    {
        croak("This should never happen");
    }

CDROM_ADDR*
add (addr1, addr2, ...)
        CDROM_ADDR *addr1;
        SV *addr2;
    PREINIT:
        CDROM_ADDR *delta;
        int lba1, lba2;
    CODE:
    {
        lba1 = addr1->addr.lba;
        
        /* second argument could be object or an integer */
        if (!sv_isobject(addr2))
            lba2 = SvIV(addr2);
        else {
            CDROM_ADDR *a = (CDROM_ADDR*) SvIV( (SV*)SvRV(addr2) );
            lba2 = a->addr.lba;
        }
           
        New(0, delta, 1, CDROM_ADDR);
        delta->type = CDROM_LBA;
        delta->addr.lba = lba1 + lba2;
        RETVAL = delta;
    }
    OUTPUT:
        RETVAL

CDROM_ADDR*
sub (addr1, addr2, swap)
        CDROM_ADDR *addr1;
        SV *addr2;
        IV swap;
    PREINIT:
        CDROM_ADDR *delta;
        int lba1, lba2;
    CODE:
    {
        lba1 = addr1->addr.lba;

        /* second argument could be object or an integer */
        if (!sv_isobject(addr2))
            lba2 = SvIV(addr2);
        else {
            CDROM_ADDR *a = (CDROM_ADDR*) SvIV( (SV*)SvRV(addr2) );
            lba2 = a->addr.lba;
        }
        
        New(0, delta, 1, CDROM_ADDR);
        delta->type = CDROM_LBA;
        delta->addr.lba = swap ? lba2 - lba1 : lba1 - lba2;
        RETVAL = delta;
    }
    OUTPUT:
        RETVAL


CDROM_ADDR*
new (CLASS, type, ...)
        char *CLASS;
        int type;
    PREINIT:
        CDROM_ADDR *addr;
    CODE:
    {
        if (type == CDROM_LBA) { 
            if (items != 3)
                croak("Usage: Linux::CDROM::Addr->new(CDROM_LBA, $frame)");
            else {
                New(0, addr, 1, CDROM_ADDR);
                addr->addr.lba = POPi;
            }
        } 
        else if (type == CDROM_MSF) {
            if (items != 5)
                croak("Usage: Linux::CDROM::Addr->new(CDROM_MSF, $min, $sec, $frame)");
            else {
                New(0, addr, 1, CDROM_ADDR);
                addr->addr.msf.minute   = (__u8)SvIV(ST(2));
                addr->addr.msf.second   = (__u8)SvIV(ST(3));
                addr->addr.msf.frame    = (__u8)SvIV(ST(4));
            }
        }
        else 
            croak("First argument to Linux::CDROM::Addr->new() must be either CDROM_LBA or CDROM_MSF");

        addr->type = type;
        to_lba(addr);
        RETVAL = addr;
    }
    OUTPUT:
        RETVAL

int
frame (self)
        CDROM_ADDR *self;
    PREINIT:
        int lba;
        char min, sec, frame;
    CODE:
    {
        lba = self->addr.lba;
        lba_to_msf(lba, &min, &sec, &frame);
        RETVAL = frame;
    }
    OUTPUT:
        RETVAL

int
second (self)
        CDROM_ADDR *self;
    PREINIT:
        int lba;
        char min, sec, frame;
    CODE:
    {
        lba = self->addr.lba;
        lba_to_msf(lba, &min, &sec, &frame);
        RETVAL = sec;
    }
    OUTPUT:
        RETVAL

int
minute (self)
        CDROM_ADDR *self;
    PREINIT:
        int lba;
        char min, sec, frame;
    CODE:
    {
        lba = self->addr.lba;
        lba_to_msf(lba, &min, &sec, &frame);
        RETVAL = min;
    }
    OUTPUT:
        RETVAL

int
as_lba (self)
        CDROM_ADDR *self;
    CODE:
    {
        RETVAL = self->addr.lba;
    }
    OUTPUT:
        RETVAL
        
void
as_msf (self)
        CDROM_ADDR *self;
    PREINIT:
        int lba;
        char min, sec, frame;
    CODE:
    {
        lba = self->addr.lba;
        lba_to_msf(lba, &min, &sec, &frame);
        ST(0) = sv_2mortal(newSVuv(min));
        ST(1) = sv_2mortal(newSVuv(sec));
        ST(2) = sv_2mortal(newSVuv(frame));
        XSRETURN(3);
    }
        
void
DESTROY (self)
        CDROM_ADDR *self;
    CODE:
    {
        Safefree(self);
    }

MODULE = Linux::CDROM      PACKAGE = Linux::CDROM::Subchannel

int
status (self) 
        CDROM_SUBCHANNEL *self;
    CODE:
    {
        RETVAL = self->cdsc_audiostatus;
    }
    OUTPUT:
        RETVAL

CDROM_ADDR*
abs_addr (self)
        CDROM_SUBCHANNEL *self;
    PREINIT:
        CDROM_ADDR *addr;
    CODE:
    {
        New(0, addr, 1, CDROM_ADDR);
        addr->type = CDROM_LBA;
        addr->addr = self->cdsc_absaddr;
        RETVAL = addr;
    }
    OUTPUT:
        RETVAL
        
CDROM_ADDR*
rel_addr (self)
        CDROM_SUBCHANNEL *self;
    PREINIT:
        CDROM_ADDR *addr;
    CODE:
    {
        New(0, addr, 1, CDROM_ADDR);
        addr->type = CDROM_LBA;
        addr->addr = self->cdsc_reladdr;
        RETVAL = addr;
    }
    OUTPUT:
        RETVAL

int
track (self)
        CDROM_SUBCHANNEL *self;
    CODE:
    {
        RETVAL = self->cdsc_trk;
    }
    OUTPUT:
        RETVAL

int
index (self)
        CDROM_SUBCHANNEL *self;
    CODE:
    {
        RETVAL = self->cdsc_ind;
    }
    OUTPUT:
        RETVAL

void
DESTROY (self)
        CDROM_SUBCHANNEL *self;
    CODE:
    {
        Safefree(self);
    }

MODULE = Linux::CDROM      PACKAGE = Linux::CDROM::TocEntry

CDROM_ADDR*
addr (self)
        CDROM_TOCENTRY *self;
    PREINIT:
        CDROM_ADDR *addr;
    CODE:
    {
        New(0, addr, 1, CDROM_ADDR);
        addr->type = CDROM_LBA;
        addr->addr = self->cdte_addr;
        RETVAL = addr;
    }
    OUTPUT:
        RETVAL

int
adr (self)
        CDROM_TOCENTRY *self;
    CODE:
    {
        RETVAL = self->cdte_adr;
    }
    OUTPUT:
        RETVAL

int
is_data (self)
        CDROM_TOCENTRY *self;
    CODE:
    {
        RETVAL = (self->cdte_ctrl & CDROM_DATA_TRACK) > 0;
    }
    OUTPUT:
        RETVAL

int
is_audio (self)
        CDROM_TOCENTRY *self;
    CODE:
    {
        RETVAL = (self->cdte_ctrl & CDROM_DATA_TRACK) == 0;
    }
    OUTPUT:
        RETVAL

void
DESTROY (self)
        CDROM_TOCENTRY *self;
    CODE:
    {
        Safefree(self);
    }

MODULE = Linux::CDROM      PACKAGE = Linux::CDROM::Format

void
raw2yellow1 (CLASS, data)
        char *CLASS;
        char *data;
    CODE:
    {
        EXTEND(SP, 6);
        ST(0) = sv_2mortal(newSVpvn(data, 12));         /* sync */
        ST(1) = sv_2mortal(newSVpvn(data+12, 4));       /* head */
        ST(2) = sv_2mortal(newSVpvn(data+16, 2048));    /* data */
        ST(3) = sv_2mortal(newSVpvn(data+2064, 4));     /* EDC */
        ST(4) = sv_2mortal(newSVpvn(data+2068, 8));     /* zero */
        ST(5) = sv_2mortal(newSVpvn(data+2076, 276));   /* ECC */
        XSRETURN(6);
    }

void
raw2yellow2 (CLASS, data)
        char *CLASS;
        char *data;
    CODE:
    {
        EXTEND(SP, 3);
        ST(0) = sv_2mortal(newSVpvn(data, 12));         /* sync */
        ST(1) = sv_2mortal(newSVpvn(data+12, 4));       /* head */
        ST(2) = sv_2mortal(newSVpvn(data+16, 2336));    /* data */
        XSRETURN(3);
    }

void
raw2green1 (CLASS, data)
        char *CLASS;
        char *data;
    CODE:
    {
        EXTEND(SP, 6);
        ST(0) = sv_2mortal(newSVpvn(data, 12));         /* sync */
        ST(1) = sv_2mortal(newSVpvn(data+12, 4));       /* head */
        ST(2) = sv_2mortal(newSVpvn(data+16, 8));       /* sub */
        ST(3) = sv_2mortal(newSVpvn(data+24, 2048));    /* data */
        ST(4) = sv_2mortal(newSVpvn(data+2072, 4));     /* EDC */
        ST(5) = sv_2mortal(newSVpvn(data+2076, 276));   /* ECC */
        XSRETURN(6);
    }

void
raw2green2 (CLASS, data)
        char *CLASS;
        char *data;
    CODE:
    {
        EXTEND(SP, 5);
        ST(0) = sv_2mortal(newSVpvn(data, 12));         /* sync */
        ST(1) = sv_2mortal(newSVpvn(data+12, 4));       /* head */
        ST(2) = sv_2mortal(newSVpvn(data+16, 8));       /* sub */
        ST(3) = sv_2mortal(newSVpvn(data+24, 2324));    /* data */
        ST(4) = sv_2mortal(newSVpvn(data+2348, 4));     /* EDC */
        XSRETURN(5);
    } 

void
wav_header (CLASS, bytes)
        char *CLASS;
        unsigned int bytes;
    CODE:
    {
        struct {
            char	RiffTag[4]; 	// 'RIFF'
            int32_t	FileLength;
            char	FormatTag[8];	// 'WAVEfmt '
            int32_t	FormatLength;	// 16
            int16_t	DataFormat;
            int16_t	NumChannels;
            int32_t	SampleRate;
            int32_t	BytesPerSecond;
            int16_t	BlockAlignment;
            int16_t	SampleDepth;
            char	DataTag[4];	// 'data'
            int32_t	DataLength;
        } header = {
            "RIFF", 0, "WAVEfmt ",
            16, 1, 2, 44100, 176400, 4, 16,
            "data", 0
        };
        header.FileLength = 36 + bytes;
        header.DataLength = bytes;
        ST(0) = sv_newmortal();
        sv_setpvn(ST(0), (char*) &header, sizeof(header));
        XSRETURN(1);
    }