//
// This is part of dvd+rw-tools by Andy Polyakov <appro@fy.chalmers.se>
//
// Use-it-on-your-own-risk, GPL bless...
//
// For further details see http://fy.chalmers.se/~appro/linux/DVD+RW/
//
#if defined(__linux)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/cdrom.h>
#include <errno.h>
#include <string.h>
#include <mntent.h>
#include <sys/wait.h>

typedef enum {	NONE=CGC_DATA_NONE,
		READ=CGC_DATA_READ,
		WRITE=CGC_DATA_WRITE
	     } Direction;

class Scsi_Command {
private:
    int fd,autoclose;
    char *filename;
    struct cdrom_generic_command cgc;
    union {
	struct request_sense	s;
	unsigned char		u[18];
    } _sense;
public:
    Scsi_Command()	{ fd=-1, autoclose=1; filename=NULL; }
    Scsi_Command(int f)	{ fd=f,  autoclose=0; filename=NULL; }
    ~Scsi_Command()	{ if (fd>=0 && autoclose) close(fd),fd=-1;
			  if (filename) free(filename),filename=NULL;
			}
    int associate (char *file)
    { struct stat sb;

	if ((fd=open (file,O_RDONLY|O_NONBLOCK)) < 0)	return 0;
	if (fstat(fd,&sb) < 0)				return 0;
	if (!S_ISBLK(sb.st_mode))	{ errno=ENOTBLK;return 0; }

	filename=strdup(file);

	return 1;
    }
    unsigned char &operator[] (size_t i)
    {	if (i==0)
	{   memset(&cgc,0,sizeof(cgc)), memset(&_sense,0,sizeof(_sense));
	    cgc.quiet = 1;
	    cgc.sense = &_sense.s;
	}
	return cgc.cmd[i];
    }
    unsigned char &sense(size_t i)  { return _sense.u[i]; }
    unsigned char *sense()	    { return _sense.u;    }
    void timeout(int i)		    { cgc.timeout=i*1000; }
    int transport(Direction dir=NONE,void *buf=NULL,size_t sz=0)
    {	cgc.buffer = (unsigned char *)buf;
	cgc.buflen = sz;
	cgc.data_direction = dir;
	return ioctl(fd,CDROM_SEND_PACKET,&cgc) != 0;
    }
    int umount()
    { struct stat    fsb,msb;
      struct mntent *mb;
      FILE          *fp;
      pid_t          pid,rpid;
      int            ret=0,rval;

	if (fstat (fd,&fsb) < 0)		return -1;
	if ((fp=setmntent (MOUNTED,"r"))==NULL)	return -1;

	while ((mb=getmntent (fp))!=NULL)
	{   if (stat (mb->mnt_fsname,&msb) < 0) continue; // corrupted line?
	    if (msb.st_rdev == fsb.st_rdev)
	    {	ret = -1;
		if ((pid = fork()) == (pid_t)-1)	break;
		if (pid == 0) execl ("/bin/umount","umount",mb->mnt_dir);
		while (1)
		{   rpid = waitpid (pid,&rval,0);
		    if (rpid == (pid_t)-1)
		    {	if (errno==EINTR)	continue;
			else			break;
		    }
		    else if (rpid != pid)
		    {	errno = ECHILD;
			break;
		    }
		    if (WIFEXITED(rval))
		    {	if (WEXITSTATUS(rval) == 0) ret=0;
			else			    errno=EBUSY; // most likely
			break;
		    }
		    else
		    {	errno = ENOLINK;	// some phony errno
			break;
		    }
		}
		break;
	    }
	}
	endmntent (fp);

	return ret;
    }
    int reload()
    { int f=-1;
#if 0
	if (ioctl (fd,CDROM_MEDIA_CHANGED,0) == 0)
#else
	if ((f=open (filename,O_RDWR|O_NONBLOCK)) < 0 && errno == EROFS)
#endif
	{   if (ioctl (fd,CDROMEJECT))		return 1;
	    if (ioctl (fd,CDROMCLOSETRAY))	return 1;
	}
	if (f>=0) close(f);
	return 0;	
    }
};

#elif defined(_WIN32)

#include <windows.h>
#include <devioctl.h>
#include <ntddscsi.h>

typedef enum {	NONE=SCSI_IOCTL_DATA_UNSPECIFIED,
		READ=SCSI_IOCTL_DATA_IN,
		WRITE=SCSI_IOCTL_DATA_OUT
	     } Direction;

typedef struct {
    SCSI_PASS_THROUGH_DIRECT	spt;
    unsigned char		sense[18];
} SPKG;

class Scsi_Command {
private:
    HANDLE fd;
    SPKG   p;
public:
    Scsi_Command()	{ fd=INVALID_HANDLE_VALUE; }
    ~Scsi_Command()	{ if (fd!=INVALID_HANDLE_VALUE) CloseHandle (fd); }
    int associate (char *file)
    { char dev[32];
	sprintf(dev,"\\\\.\\%.*s",sizeof(dev)-5,file);
	fd=CreateFile (dev,GENERIC_WRITE|GENERIC_READ,
			   FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
	return fd!=INVALID_HANDLE_VALUE;
    }
    unsigned char &operator[] (size_t i)
    {	if (i==0)
	{   memset(&p,0,sizeof(p));
	    p.spt.Length = sizeof(p.spt);
	    p.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
	    p.spt.TimeOutValue = 30;
	    p.spt.SenseInfoLength = sizeof(p.sense);
	    p.spt.SenseInfoOffset = offsetof(SPKG,sense);
	}
	p.spt.CdbLength = i+1;
	return p.spt.Cdb[i];
    }
    unsigned char &sense(size_t i)  { return p.sense[i]; }
    unsigned char *sense()	    { return p.sense;    }
    void timeout(int i)		    { p.spt.TimeOutValue=i; }
    int transport(Direction dir=NONE,unsigned char *buf=NULL,size_t sz=0)
    { DWORD bytes;
	p.spt.DataBuffer = buf;
	p.spt.DataTransferLength = sz;
	p.spt.DataIn = dir;
	return DeviceIoControl (fd,IOCTL_SCSI_PASS_THROUGH_DIRECT,
				&p,sizeof(p.spt),
				&p,sizeof(p),
				&bytes,FALSE) == 0 || p.sense[0]&0x70;
    }
};

static void perror (const char *str)
{ LPVOID lpMsgBuf;

    FormatMessage( 
	FORMAT_MESSAGE_ALLOCATE_BUFFER |
	FORMAT_MESSAGE_FROM_SYSTEM | 
	FORMAT_MESSAGE_IGNORE_INSERTS,
	NULL,
	GetLastError(),
	0, // Default language
	(LPTSTR) &lpMsgBuf,
	0,
	NULL 
	);
    if (str)
	fprintf (stderr,"%s: %s",str,lpMsgBuf);
    else
	fprintf (stderr,"%s",lpMsgBuf);

    LocalFree(lpMsgBuf);
}

#else
#error "Unsupported OS"
#endif

#include <poll.h>
#include <sys/time.h>

int wait_for_unit (Scsi_Command &cmd,volatile int *progress)
{ unsigned char *sense=cmd.sense(),sensebuf[18];
  int msecs=1000;
  struct timeval tv;

    while (1)
    {	if (msecs > 0) poll(NULL,0,msecs);
	gettimeofday (&tv,NULL);
	msecs = tv.tv_sec*1000+tv.tv_usec/1000;
	cmd[0] = 0;	// TEST UNIT READY
	cmd[5] = 0;
	if (!cmd.transport ()) break;
	// I wish I could test just for sense.valid, but (at least)
	// hp dvd100i returns 0 in valid bit at this point:-( So I
	// check for all bits...
	if ((sense[0]&0x70)==0)
	{   perror ("- [unable to TEST UNIT READY]");
	    return 1;
	}

	while (progress)
	{   if (sense[15]&0x80)
	    {	*progress = sensebuf[16]<<8|sensebuf[17];
		break;
	    }
	    // MMC-3 (draft) specification says that the unit should
	    // return progress indicator in key specific bytes even
	    // in reply to TEST UNIT READY. I.e. as above! But (at
	    // least) hp dvd100i doesn't do that and I have to fetch
	    // it separately:-(
	    cmd[0] = 0x03;	// REQUEST SENSE
	    cmd[4] = sizeof(sensebuf);
	    cmd[5] = 0;
	    if (cmd.transport (READ,sensebuf,sizeof(sensebuf)))
	    {   perror ("- [unable to REQUEST SENSE]");
		return 1;
	    }
	    if (sensebuf[15]&0x80)
		*progress = sensebuf[16]<<8|sensebuf[17];
	    break;
	}
	gettimeofday (&tv,NULL);
	msecs = 1000 - (tv.tv_sec*1000+tv.tv_usec/1000 - msecs);
    }

  return 0;
}

