//
// Copyright (c) 1997 Jean-Marc Zucconi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer
//    in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <string.h>
#include <sys/cdio.h>
#include <arpa/inet.h>
#include <machine/endian.h>

#include "cd-write.h"

void
dump_info (union ccb *ccb)
{
    int i;
    char msg[300];
    for (i = 0; i < ccb->csio.cdb_len; i++) {
	sprintf (msg, "%02x ", ccb->csio.cdb_io.cdb_bytes[i]);
	Info (msg, 3);
    }
    sprintf (msg, "\nsense_len=%d status=0x%02x sense_resid=0x%02x resid=0x%x",
	     ccb->csio.sense_len, ccb->csio.scsi_status, ccb->csio.sense_resid,
	     ccb->csio.resid);
    Info (msg);
    for (i = 0; i < ccb->csio.sense_len; i++) {
	sprintf (msg, "%02x ", ((unsigned char *)&ccb->csio.sense_data)[i]);
	Info (msg, 3);
    }
    if (ccb->csio.sense_len)
	Info ("\n", 1);
}

struct dev_info *
inquiry (cam_device *cam_dev)
{
    int i;
    struct dev_info *info;

    struct { // from /sys/scsi/scsi_all.h
        u_char  device;
        u_char  dev_qual2;
        u_char  version;
        u_char  response_format;
        u_char  additional_length;
        u_char  unused[2];
        u_char  flags;
        char    vendor[8];
        char    product[16];
        char    revision[4];
        u_char  extra[60];
    } data;

    info = (dev_info *) malloc (sizeof (struct dev_info));
    info->type = cam_dev->inq_data.device  & 0x1f;
    info->vendor = (char *) malloc (10);
    strncpy (info->vendor, cam_dev->inq_data.vendor, 8);
    info->vendor[8] = 0;
    for (i = 7; i >= 0 && info->vendor[i] == ' '; i--)
	info->vendor[i] = 0;
    info->product = (char *) malloc (18);
    strncpy (info->product, cam_dev->inq_data.product, 16);
    info->product[16] = 0;
    for (i = 15; i >= 0 && info->product[i] == ' '; i--)
	info->product[i] = 0;
    info->revision = (char *) malloc (6);
    strncpy (info->revision, cam_dev->inq_data.revision, 4);
    info->revision[4] = 0;
    for (i = 3; i >= 0  && info->revision[i] == ' '; i--)
	info->revision[i] = 0;
    return info;
}

struct toc *
cd_toc (cam_device *fd)
{
    struct {
	struct ioc_toc_header header;
	struct cd_toc_entry entries[100];
    } data;
    int n, c;
    struct toc *t;
    union ccb *ccb;
    char cmd[10] = {0x43, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    cmd[7] = sizeof (data) >> 8;
    cmd[8] = sizeof (data) & 0xff;

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 0;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_IN, (unsigned char *)&data, sizeof (data), cmd, 20000);
    data.header.len = 0;
    if (cam_send_ccb (fd, ccb) < 0) {
	dump_info (ccb);
	cam_freeccb (ccb);
	return 0;
    }
    cam_freeccb (ccb);
    
    n = ntohs (data.header.len) / sizeof (struct cd_toc_entry);
    if (!n)
	return 0;
    t = (struct toc *) malloc (sizeof (struct toc) * n);
    c = 0;
    while (n--) {
	t[c].track = data.entries[c].track;
	t[c].type = data.entries[c].control & 4 ? T_DATA : T_AUDIO;
	if (t[c].type == T_AUDIO &&data.entries[c].control & 1)
	    t[c].type |= T_PREEMP;
	t[c].copy = data.entries[c].control & 2 ? 1 : 0;
	t[c].channels = data.entries[c].control & 8 ? 4 : 2;
	t[c].addr = ntohl (data.entries[c].addr.lba);
	t[c].m = (t[c].addr +2*75)/(60*75);
	t[c].s = (t[c].addr +2*75 - t[c].m*60*75)/75;
	t[c].f = t[c].addr +2*75 - t[c].m*60*75 - t[c].s*75;
	c++;
    }
    return t;
}

int
test_unit_ready (cam_device *fd)
{
    union ccb *ccb;
    char cmd[6] = {0, 0, 0, 0, 0, 0};
    int c;

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);

    if (cam_send_ccb(fd, ccb) < 0) {
	c = ccb->csio.sense_data.info[0] & 0xf;
	if (c == 1) {
	    cam_freeccb (ccb);
	    return 1;
	}
	Info ("TEST UNIT READY command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return -1;
    }
    cam_freeccb (ccb);
    return 1;
}

void
prevent_removable (cam_device *fd, int yes)
{
    union ccb *ccb;
    char cmd[6] = {0x1e, 0, 0, 0, 0, 0};

    cmd[4] = yes ? 1 : 0;
    ccb = cam_getccb (fd);
    if (!ccb)
	return;

    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);
    
    cam_send_ccb(fd, ccb);
    cam_freeccb (ccb);
    return;
}
int
rezero_unit (cam_device *fd)
{
    union ccb *ccb;
    char cmd[6] = {0x1, 0, 0, 0, 0, 0};
    int c;

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);
    
    if (cam_send_ccb(fd, ccb) < 0)
	c = ccb->csio.sense_data.info[0] & 0xf;
    else
	c = 0;
    cam_freeccb (ccb);
    return c;
}
int
start_unit (cam_device *fd)
{
    union ccb *ccb;
    char cmd[6] = {0x1b, 0, 0, 0, 1, 0};

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);
    
    if (cam_send_ccb(fd, ccb) < 0) {
        Info ("start unit command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 1;
    }
    cam_freeccb (ccb);
    return 0;
}
int
open_tray (cam_device *fd)
{
    union ccb *ccb;
    char cmd[6] = {0x1b, 0, 0, 0, 2, 0};

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);
    
    if (cam_send_ccb(fd, ccb) < 0) {
        Info ("open tray command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 1;
    }
    cam_freeccb (ccb);
    return 0;
}
int
close_tray (cam_device *fd)
{
    union ccb *ccb;
    char cmd[6] = {0x1b, 0, 0, 0, 3, 0};

    ccb = cam_getccb (fd);
    if (!ccb) {
        Info ("CAM: error allocating ccb");
	return 1;
    }
    bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
    CFILL (ccb, CAM_DIR_NONE, 0, 0, cmd, 20000);
    
    if (cam_send_ccb(fd, ccb) < 0) {
        Info ("close tray command failed");
	dump_info (ccb);
	cam_freeccb (ccb);
	return 1;
    }
    cam_freeccb (ccb);
    return 0;
}
