Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 15 Jun 2011 16:41:08 +0100
From:      Frank Mitchell <mitchell@wyatt672earp.force9.co.uk>
To:        ukfreebsd@uk.freebsd.org, freebsd-chat@freebsd.org, freebsd-hackers@freebsd.org
Subject:   New Spiegel CD/DVD Burning Utility
Message-ID:  <201106151641.08993.mitchell@wyatt672earp.force9.co.uk>

next in thread | raw e-mail | index | archive | help
I've invented an alternative to cdrecord/wodim and burncd, naming it "spiegel".
Here's the Source Code for Linux. I tried a FreeBSD version too, but it has to be said:
The Linux version works better. Hope you like the Wildebeest License.

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* README.TXT - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

	README

Welcome to Spiegel, a simpler way to burn Iso Images on Optical Media. This
Readme describes the Linux version, which needs Kernel 2.6.16 minimum to
access Device Files (/dev/sr0 etc) using the SG_IO ioctl. Some debugging
happened around Kernel 2.6.24, so a later version seems advisable.

Spiegel assumes you're using an MMC-2 Compatible CD/DVD Writer. If it writes
DVDs, or even if it just has Buffer Underrun Protection, the MMC-2 Standard
should apply. Non-compatible devices probably haven't been marketed since
the 1990s.

This First Release is Spiegel v2.0 for Linux. This writes commonly available
CD and DVD media, excluding Double-Layer disks, and DVD+RW which can't be
used for Sequential Recording. Other RW media must be blanked before re-use,
preferably completely, but not formatted.

Spiegel is an interactive Console Utility, which uses Real-Time Priority
and thus needs to be run by Root. This First Release deals with problems
merely by trying to detect them at an early stage and crashing harmlessly
with an Error Message.

To facilitate burning multiple copies of Iso Images with complicated names,
you can set a Symbolic Link: "IsoLink" to your Iso Image Pathname. Spiegel
has some Command Line Options too. Brackets [] indicate Default Values:

	-b INT: Set Write Buffer Ring Factor 10-20 [11]
Spiegel needs a ring of Memory Buffers with a total size larger than the
capacity of your CD/DVD Writer Cache. Taking the unit as 1/8 the size of
your Hardware Cache, the range 10 - 20 gives a factor 1.25 - 2.5 larger.

	-c INT: Set CD Speed Limit [Max KB/Sec]
[4x=706,8x=1411,10x=1764,16x=2822,24x=4234,32x=5645,40x=7056,48x=8467]
When you set the Write Speed, it usually works as a Speed Limit. You can
specify a higher number, and your drive will select a lower value based on
the Optical Power Calibration of your media. CDs and DVDs use entirely
different ranges, so it's clearer to specify the value in Kilobytes/Second.
Common CD values are listed above.

	-d INT: Set DVD Speed Limit [Max KB/Sec]
[1x=1385,2x=2770,4x=5540,6x=8310,8x=11080,12x=16620,16x=22160]
These are the common values for DVD Speeds. If you need to impose limits,
it's useful to keep the Command Line in a tiny Script File, with the CD and
DVD Speed Limits set independently.

	-h    : Display Brief Help
Reminds you of these options.

	-p INT: Set Real-Time Priority [Mid-Range]
[Probably 1 (Lowest) - 99 (Highest) with Default = 50]
Spiegel uses Real-Time Priorities so the Write Process gets precedence over
other User Tasks. If you're not the Super-User you can't set Real-Time
Priorities, and Spiegel will crash harmlessly.

	-v    : Display Version & Licensing Message
Says Spiegel v2.0 is Copyright 2011 by Frank Mitchell, and released under
the Wildebeest License, shown below.

	RUNNING SPIEGEL

Example Compile command with output "Make.out", containing details of the
Link Stage:
	"make -kB >& Make.out"

For Sequential Recording, CD/DVD RW media must be blanked before re-use,
preferably completely, but not formatted:
	"wodim blank=all dev=/dev/scd0 -v"

You may need to limit the Write Speed if you want your disks to be readable
in other drives besides your own DVD Re-Writer. For instance: I have an
ATAPI UDMA-66 DVD Burner, connected with the required 80-Wire PATA Cable.
And I also have an ATAPI UDMA-33 DVD Read-Only Drive, connected with an old
40-Wire PATA Cable. I can record DVD-R at 16x Speed = 22160 KB/Sec, but my
DVD Reader will only see DVD-R recorded at 12x Speed = 16620 KB/Sec. DVD+R
doesn't seem to have this problem.

CD-R can benefit from Test Runs to decide what Write Speed you want. My
Write Runs always use Buffer Under-Run Protection, but Constant Angular
Velocity implies that the Write Speed keeps increasing. So I prefer the
original technique of using Test Runs Without Buffer Under-Run Protection
until I don't see the message: "RUN FAILED: WRITE COMMAND ERROR". This
happens at 48x Speed = 8467 KB/Sec, but I can solve the problem by
increasing the Real-Time Priority from 50 to 75.

You'll need an Iso Image File to record, created using mkisofs/genisoimage.
I tend to select the sub-trees I want to record using Symbolic Links in a
Directory called DBACK, which then corresponds to the root of my DVD-R:
	"genisoimage -f -D -R -o Rock.iso ./DBACK"

If desired, you can set a Symbolic Link to the Iso Image Filepath:
	"ln -sf /mnt/OS/IMG/Rock.iso IsoLink"
Spiegel accepts "IsoLink" as the Default, so you can get your filename just
by pressing Return.

Here's what an Interactive Run looks like:

./spiegel20
"Spiegel Uses Real-Time Priority: Be Root!"
"DISK NEEDED NOW"
	"Proceed? [Y]"
[Ret]
	"SPECIFY DEVICE IN /dev/ [sr0]"
[Ret]
"DEVICE FILE OPENED, UNIT STARTED, UNIT TESTED READY"
"Current Profile: 000Ah, Re-Writable CD-RW"
"Max Read & Write Speeds KB/Sec Now: 1764, 1764"
"Capacity Already Used: 239.5 Meg"
"Before Run Free Space: 441.1 Meg"
	"Choose Iso Image Path [IsoLink]"
[Ret]
"Space Needed: 127.7 Meg, Free Space Fraction: 28.95%"
"After Run Free Space < 313.4 Meg"
	"Allow Further Sessions? [Y]"
[Ret]
	"Set Write Speed KB/Sec [1764]"
[Ret]
"Max Read & Write Speeds KB/Sec Now: 7056, 1764"
	"Will This Be A Test Run? [Y]"
[Ret]
	"Use Under-Run Protection? [N]"
[Ret]
	"Last Chance To Abandon Run! Proceed? [Y]"
[Ret]
"WAIT UNTIL CLOSE SESSION MESSAGE APPEARS"
"Skip CLOSE SESSION In Test Mode"
	"That Was A Test: Repeat Run? [Y]"
[Ret]
	"Set Write Speed KB/Sec [1764]"
[Ret]
"Max Read & Write Speeds KB/Sec Now: 7056, 1764"
	"Will This Be A Test Run? [Y]"
N
	"Last Chance To Abandon Run! Proceed? [Y]"
[Ret]
"WAIT UNTIL CLOSE SESSION MESSAGE APPEARS"
"WAIT DURING CLOSE SESSION"
"Predicted Time Remaining: 15.1 Sec"
"Completed CLOSE SESSION"
"Max Read & Write Speeds KB/Sec Now: 7056, 1764"
"REMOVE DISK NOW"

For Multi-Session you need to supply mkisofs/genisoimage with the Start
Address of the Session you want to import, and the Start Address of the
Session you want to write next. You can get these numbers from the READ
TRACK INFORMATION sections in Spiegel.log, but you need to perform a repeat
Dummy Run of Spiegel.

After your first Session, you'll discover that READ TRACK INFORMATION lists
TWO Sessions and TWO Tracks. This is because the unwritten area of the disk
is treated as a Track, called the "Invisible Track". So its Start Address is
the Next Writable Address, and its Length corresponds to the Free Space left
on your disk. Thus after your Initial Session you get:
	Track #1, Session #1: Track Start Address = 0 (For Importing)
	Track #2, Session #2: Track Start Address = Next Writable Address
Those two numbers are the ones you need to quote using the -C Option when
you Import data from one Session to the next on a Multi-Session Disk.

READ TRACK INFORMATION is an MMC Command, and all the Spiegel Source Code
revolves around these. For more information about MMC, try to get the T10
Standards "mmc2r11a.pdf" and "spc2r20.pdf", also the later versions, which
contain the same information but getting more complicated.

	CHANGELOG

First Release: Spiegel v2.0 for Linux, Copyright 2011 by Frank Mitchell.
Contact: mitchell@wyatt672earp.force9.co.uk. This uses Sequential Recording
to write a single file to commonly available CD and DVD Media. Spiegel is a
recent invention, but I have tested it and started using it for real.
Incidentally, "Spiegel" is German for "Mirror".

	TODO

This first release of Spiegel v2.0 dated June 2011, has only been tried by
the original author Frank Mitchell. Some unresolved issues can be expected,
involving the Documentation if not the Program itself.

Spiegel is still untidy, with a mixture of Function Parameters and Global
Variables. I left this for now. Maybe this is one type of Application where
it makes sense to use Global Variables. And some people won't agree with the
many goto Statements, a legacy from years of Fortran Programming. Note how
most of these are used to crash out to an Error Message. You'll notice I'm
fond of CAPITAL LETTERS too: That's from Fortran again. And the Comment
Statements which follow Curly Brackets are actually Footnotes which describe
the code above.

Spiegel.log is informative, and shows how much data can be collected from
your CD/DVD Drive just by asking. But much of the information could be
redundant, depending how you want to develop the application further.

A FreeBSD version of Spiegel is possible, using ioctl IOCATAREQUEST with
struct ata_ioc_request. But I understand this interface will change shortly.
And for some reason the Write Speed for CD-R on my drive is limited under
FreeBSD. I get Buffer Under-Runs above 24x = 4234 KB/Sec.

	WILDEBEEST LICENSE

PREAMBLE: The Licenses for most Software are designed to repudiate any legal
liability if it doesn't work. By contrast, the Wildebeest License tries to
ensure that it will work, and that you know about any problems beforehand.
No permission is needed to modify your Software to serve its intended
purpose, because United States and European Union Law both allow Lawful
Users to do this anyway. So when this License speaks of Free Software, we
mean that you don't need to pay money for it, not that you can modify it
until it stops working and nobody understands why. Open Source Users will be
aware of such problems when using Free Software, and check for reliability
before depending on it. So instead of including a Warranty Disclaimer which
could be invalid, the Wildebeest License seeks to ensure that reliablity
issues are documented. Note that Software is not patentable under European
Law, though it can be covered by a patent for another invention which is.
Also, Multiple Licensing is possible, so you can contact the Original Author
if you believe the terms of the Wildebeest License need to be altered.

1: This version of the Wildebeest License is intended to be governed by the
Legal System of England, which entitles Lawful Users to modify Software if
necessary for their own use. You can correct it or adapt it to serve its
intended purpose, study its operation and incorporate any underlying ideas
into completely different Software licensed under other terms, and make as
many Backup or Development Copies as you wish.

2: For Users this Software is intended as a Free Gift, available free of
charge apart from incidental expenses, and free from any other obligation
beyond the provisions of Copyright and other legal requirements.

3: If you distribute this Software or a modified version to other people,
you must do so under the terms of this License. You must ensure that the
relevant Documentation and Source Code are available. If you are aware of
problems, you must check the Documentation and ensure they are described.
This could mean adding Comment Statements to the Source Code as well as
editing plaintext documents.

4: If you distribute an adaptation of this Software or a modified version,
you must update the Documentation, identifying yourself and your changes.
The Wildebeest License does not contain a Warranty Disclaimer, so this
Documentation amounts to a Limited Warranty that within the resources
available, you have tested the modified Software and that in your experience
it functions as future Users are likely to expect.

========================================================

# Copyright 2011 Frank Mitchell - Wildebeest License
# Makefile - Spiegel v2.0 - Linux Kernel 2.6.16+
# Contact: mitchell@wyatt672earp.force9.co.uk

QAFLAGS = -Wall -Wextra\
-Wshadow -Wunreachable-code -Wpointer-arith\
-Wstrict-prototypes -Wmissing-prototypes\
-Wmissing-declarations -Wredundant-decls

OBJS = driver.o iface.o cddev.o writecyc.o

spiegel20 : ${OBJS}
	cc -Wl,-t ${OBJS} -o spiegel20

driver.o : driver.c spiegel.h
	cc ${QAFLAGS} -c driver.c
iface.o : iface.c spiegel.h
	cc ${QAFLAGS} -c iface.c
cddev.o : cddev.c spiegel.h
	cc ${QAFLAGS} -c cddev.c
writecyc.o : writecyc.c spiegel.h
	cc ${QAFLAGS} -c writecyc.c

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* spiegel.h - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

#define _FILE_OFFSET_BITS 64
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>

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

#include <scsi/sg.h>

/* Version & Licensing Messages */
#define SAPPMES "Spiegel v2.0 CD/DVD Iso Image Writer, Kernel 2.6.16+"
#define CPRTMES "Copyright 2011 Frank Mitchell - Wildebeest License"

#define MINTRACKL 300 /* MMC Minimum Track Length */
#define UFSPATHL 320 /* Max UFS Pathname Bytes & Null */
#define WRITE10MAX 65536 /* Max WRITE(10) Bytes Under FreeBSD v8.0+ */

#define DEFLTDEV "sr0"
#define SPIEGELLOG "./Spiegel.log"

typedef struct {
	int XferDirn; /* Data Transfer Direction */
	char *XferP; /* Data Transfer Buffer */
	int XferLen; /* Data Transfer Length */
	uint8_t Cmd[12]; /* MMC Command*/
} MMCREQ; /* Linux ATAPI Request */

typedef char ROOTPATH[UFSPATHL]; /* UFS Root Path */
typedef char SECTOR[2048]; /* CD/DVD Sector Store */
typedef char PARMSTOR[64]; /* Write Page Storage */

#ifndef IFACE
extern int GDevFil; /* Device File */
extern int GBufFac; /* Write Buffer Ring Factor */
extern off_t GIsoSize; /* Iso File Size Bytes */
extern uint32_t GDevCache; /* CDRW Hardware Buffer Size */
extern uint32_t GSeekLBA; /* Last Valid Seek Sector */
extern ROOTPATH GIsoFilPath; /* UFS Root Path */
extern SECTOR GTransBuf; /* Global Sector Buffer */
extern PARMSTOR GWritePage; /* Write Page Spec */
extern FILE *SpiegLogF; /* Spiegel Log File */
extern MMCREQ GRequest; /* Linux ATAPI Request */
#endif

/* driver.c */
int main(int,char**); /* Driver */
void OPTEVAL(int,char**,uint32_t*,uint32_t*); /* Handle Command Line Options */
void STARTUP(void); /* Unit Startup */
uint8_t CONFIGEVAL(void); /* Hardware Configuration */
const char *PROFILES(uint16_t,uint8_t*); /* Profile Numbers */

/* iface.c */
void SYSEVAL(int,char**); /* Misc System Data */
const char *SCHEDPOLICY(int); /* Scheduling Policies */
int DOPACKET(int,const char*,MMCREQ*); /* PACKET ioctl For SCSI Command */
const char *SENSEDESCS(uint8_t); /* Sense Keys */
void SHOWINQUIRY(void); /* INQUIRY Response */
const char *PERIPHQUALS(uint8_t); /* Peripheral Qualifiers */
const char *DEVTYPES(uint8_t); /* Peripheral Device Types */
const char *ANSIVERSIONS(uint8_t); /* ANSI Versions Implemented */
void DEFLTWRITE(void); /* Reset To Default Write Page */
const char *NOTIFICATIONS(uint8_t); /* Event Status Notification Types */
void BIGEND2(void*,void*); /* Reverse 2 Bytes To Other Endian */
void BIGEND4(void*,void*); /* Reverse 4 Bytes To Other Endian */
void SPLITEND2(void*,void*,void*); /* Assemble 2 Byte Integer */
int GETYN(const char*,char); /* Get Y/N Response To Prompt */
int GETINT(const char*,int); /* Get Integer Response To Prompt */
void GETSTR(const char*,char*,const char*,size_t); /* Get String Response To Prompt */

/* cddev.c */
void DRIVEEVAL(void); /* Hardware Parameter Pages */
void SHOWCAPABILITIES(uint16_t*); /* CD Capabilities & Mechanical Status Page */
void SHOWWRITEPARAMS(const char*); /* Write Parameters Mode Page */
const char *WRITETYPES(uint8_t); /* Write Types */
const char *TRACKMODES(uint8_t); /* Track Modes */
const char *MULTISESSCLOSES(uint8_t); /* Multisession Closures */
const char *DATABLOCKTYPES(uint8_t); /* Data Block Types */
const char *SESSIONFORMATS(uint8_t); /* Session Format Types */
int DISCEVAL(uint16_t*,uint16_t*,uint16_t*); /* Disc Evaluation */
int DISCINFO(uint16_t*,uint16_t*,uint16_t*); /* CDB 0x51, READ DISC INFORMATION */
const char *DISCSTATUSS(uint8_t); /* Disc Statuses */
const char *LASTSESSIONS(uint8_t); /* Last Session States */
const char *DISCTYPES(uint8_t); /* Disc Types */
uint32_t SESSEVAL(uint32_t*,uint16_t,uint16_t); /* Sessions Evaluation */
uint32_t TRACKINFO(uint32_t*); /* CDB 0x52, READ TRACK INFORMATION */
const char *DATAMODES(uint8_t); /* Data Modes */

/* writecyc.c */
uint32_t WRITESPEED(uint32_t,uint32_t,uint32_t,uint8_t,const char*); /* Limit Streaming Speed */
uint8_t WRITEPARMS(uint8_t,uint8_t); /* Set Up Write Parameters Mode Page */
int TESTBURN(uint8_t,uint32_t,uint16_t,uint8_t); /* CDR Writer Driver */
int CLOSEWAIT(uint8_t); /* CLOSE SESSION When Not Test Mode */

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* driver.c - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

#include "spiegel.h"

int main(int argc,char **argv) {
	const char *Here="@MAIN";
	uint8_t WriteMode,NextSess,CloseFunc;
	int Kyn,DiscStatus,IsoFildes;
	uint32_t WriteStart,WriteEnd,IsoBlocks,
	  NeedBlocks,FreeBlocks,CdSpeed,DvdSpeed,WritePerf;
	uint16_t FirstTrackN,LastTrackN,SessTot;
	float Frac,FreeMeg,NeedMeg;
	struct stat IsoStat;
	printf("Spiegel Uses Real-Time Priority: Be Root!\n");
/* Command Line Options & Log Files */
	OPTEVAL(argc,argv,&CdSpeed,&DvdSpeed);
	SYSEVAL(argc,argv); /* Check System Data */
	STARTUP(); /* Unit Startup */
	DRIVEEVAL(); /* Read Hardware Data Pages */
	WriteMode=CONFIGEVAL(); /* Check Profile List For Write Type Needed */
/* Overall Streaming Speeds */
	WriteStart=0; /* Negative Values Not Accepted */
	WriteEnd=0x7FFFFFFF;
	WritePerf=(WriteMode&0x01)?CdSpeed:DvdSpeed; /* CD Or DVD ? */
	WritePerf=WRITESPEED(WriteStart,WriteEnd,WritePerf,1,
	  "Initial Mixed Read/Write Streaming Speeds");
/* Read Disk Track & Session Data */
	DiscStatus=DISCEVAL(&FirstTrackN,&LastTrackN,&SessTot);
	FreeBlocks=SESSEVAL(&WriteStart,FirstTrackN,LastTrackN);
	FreeMeg=(float)FreeBlocks/512.0;
	printf("Before Run Free Space: %3.1f Meg\n",FreeMeg);
/* Can Set Symbolic Link Default: "ln -sf ImagePath.iso IsoLink" */
	GETSTR("\tChoose Iso Image Path [%s]\n",GIsoFilPath,"IsoLink",UFSPATHL);
	fprintf(SpiegLogF,"\nChosen Iso Image Path:\n\t%s\n",GIsoFilPath);
	if((IsoFildes=open(GIsoFilPath,O_RDONLY))==-1)goto BADOPENFIL;
	if(stat(GIsoFilPath,&IsoStat)==-1)goto COULDNTSTAT;
	GIsoSize=IsoStat.st_size;
	close(IsoFildes);
/* Check Enough Data Blocks Available */
	if(GIsoSize&2047)printf("WARNING: CD Image Ends With Partial Sector\n");
	IsoBlocks=(GIsoSize+2047)>>11;
	if((IsoBlocks>360000)&&(GIsoSize&32767))
	  printf("NOTE: DVD Image Ends With Partial Superblock\n");
	NeedBlocks=(IsoBlocks>MINTRACKL)?IsoBlocks:MINTRACKL;
	NeedBlocks=(WriteMode&0x01)?NeedBlocks:(((NeedBlocks+15)>>4)<<4); /* DVD Superblocks */
	NeedMeg=(float)NeedBlocks/512.0;
	Frac=NeedMeg*100.0/FreeMeg;
	printf("Space Needed: %3.1f Meg, Free Space Fraction: %4.2f%%\n",NeedMeg,Frac);
	FreeMeg=(float)((int)FreeBlocks-(int)NeedBlocks)/512.0; /* Blocks To Megabytes */
	printf("After Run Free Space < %3.1f Meg\n",FreeMeg);
	if(NeedBlocks>FreeBlocks)goto NOROOM;
	WriteEnd=WriteStart+NeedBlocks;
/* Specify Multi Session Mode */
	Kyn=GETYN("\tAllow Further Sessions? [%c]\n",'Y');
	if(Kyn=='Y') {
		NextSess=0xC6; /* Multi-Session Field=11b */
		CloseFunc=2; /* Normal CLOSE SESSION */
	} /* Leaves Disk Open */
	else {
		NextSess=0x06; /* Next Session B0 Pointer Disabled */
		CloseFunc=(WriteMode&0x08)?5:2; /* DVD+R CLOSE SESSION */
	} /* For Write-Protected Disk */
/* Write Cycle Loop */
	do {
		WritePerf=GETINT("\tSet Write Speed KB/Sec [%d]\n",WritePerf);
		WritePerf=WRITESPEED(WriteStart,WriteEnd,WritePerf,0,
		  "Independent Write Speed Set For Recorded Region");
		WriteMode=WRITEPARMS(WriteMode,NextSess);
		Kyn=GETYN("\tLast Chance To Abandon Run! Proceed? [%c]\n",'Y');
		if(Kyn=='N')break;
		Kyn=TESTBURN(WriteMode,WriteStart,LastTrackN,CloseFunc);
		if(Kyn)goto OFFLINE;
		DEFLTWRITE(); /* Clear Test Track Data */
		if((WriteMode&0x10)==0)break; /* Write Run Done */
		Kyn=GETYN("\tThat Was A Test: Repeat Run? [%c]\n",'Y');
		if(Kyn=='N')break;
	} while(1);
/* Restore Streaming Speed Defaults */
	WRITESPEED(0,0x7FFFFFFF,0x7FFFFFFF,4,"Restore Streaming Speed Defaults");
/* Stop Unit */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x1B; /* START STOP UNIT */
	GRequest.Cmd[4]=0; /* No-Unload Stop */
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"START STOP UNIT",&GRequest)!=0)goto BADSTOP;
OFFLINE:
	if(close(GDevFil)!=0)goto BADCLOSEDEV;
	fprintf(SpiegLogF,"\n%s\n%s\n",SAPPMES,CPRTMES);
/* Close Recording Log File */
	fclose(SpiegLogF);
	printf("REMOVE DISK NOW\n");
	exit(EXIT_SUCCESS);
BADOPENFIL:printf("Couldn't open() Iso File %s\n",Here);goto STOP;
COULDNTSTAT:printf("Couldn't stat() Iso File %s\n",Here);goto STOP;
NOROOM:printf("Insufficient User Blocks On Empty Track %s\n",Here);goto STOP;
BADSTOP:printf("Failed Unit Stop %s\n",Here);goto STOP;
BADCLOSEDEV:printf("Failed Device File Close %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Driver */

void OPTEVAL(int argc,char **argv,uint32_t *CdSpeedP,uint32_t *DvdSpeedP) {
	const char *Here="@OPTEVAL";
	int Opt,Arg,PrioMin,PrioMax,PrioMid;
	struct sched_param RtParm;
	GBufFac=11; /* Default Write Buffer Ring Factor */
	*CdSpeedP=0x7FFFFFFF; /* Default CD Speed Limit */
	*DvdSpeedP=0x7FFFFFFF; /* Default DVD Speed Limit */
	PrioMin=sched_get_priority_min(SCHED_FIFO);
	PrioMax=sched_get_priority_max(SCHED_FIFO);
	PrioMid=(PrioMin+PrioMax)>>1;
	RtParm.sched_priority=PrioMid; /* Default Mid-Range Real-Time Priority */
	if(sched_setscheduler(0,SCHED_FIFO,&RtParm)==-1)goto NOREALTIME;
	while((Opt=getopt(argc,argv,"b:c:d:hp:v"))!=-1) {
		switch(Opt) {
		case 'b': /* Set Write Buffer Ring Factor */
			sscanf(optarg," %d",&Arg);
			GBufFac=(Arg<10)?10:((Arg>20)?20:Arg);
			break;
		case 'c': /* Set CD Speed Limit, KB/Sec */
/* 4x=706,8x=1411,10x=1764,16x=2822,24x=4234,32x=5645,40x=7056,48x=8467 */
			sscanf(optarg," %d",CdSpeedP);
			break;
		case 'd': /* Set DVD Speed Limit, KB/Sec */
/* 1x=1385,2x=2770,4x=5540,6x=8310,8x=11080,12x=16620,16x=22160 */
			sscanf(optarg," %d",DvdSpeedP);
			break;
		default:
		case 'h': /* Display Brief Help */
			printf("Spiegel Uses Real-Time Priority: Be Root!\n");
			printf("IsoLink = Default Symbolic Link To Image File\n");
/* Write Buffer Ring Size = Device Buffer * 1.25 [10/8] - 2.5 [20/8] */
			printf("-b INT: Set Write Buffer Ring Factor 10-20 [11]\n");
			printf("-c INT: Set CD Speed Limit [Max KB/Sec]\n");
			printf("-d INT: Set DVD Speed Limit [Max KB/Sec]\n");
			printf("-h    : Display Brief Help\n");
			printf("-p INT: Set Real-Time Priority %d-%d [%d]\n",
			  PrioMin,PrioMax,PrioMid);
			printf("-v    : Display Version & Licensing Message\n");
			exit(EXIT_SUCCESS);
		case 'p': /* Set Real-Time Priority */
			sscanf(optarg," %d",&Arg);
			RtParm.sched_priority=(Arg<PrioMin)?PrioMin:((Arg>PrioMax)?PrioMax:Arg);
			sched_setscheduler(0,SCHED_FIFO,&RtParm);
			break;
		case 'v': /* Display Version & Licensing Message */
			printf("\n"SAPPMES"\n"CPRTMES"\n");
			exit(EXIT_SUCCESS);
		} /* Process Options */
	} /* Scan Command Line */
	if((SpiegLogF=fopen(SPIEGELLOG,"w"))==NULL)goto BADOPENFIL;
	fprintf(SpiegLogF,"\nCOMMAND-LINE OPTIONS\n");
	fprintf(SpiegLogF,"-b: Write Buffer Ring Factor: %d\n",GBufFac);
	fprintf(SpiegLogF,"-c: CD Speed Limit: %d Kb/Sec\n",*CdSpeedP);
	fprintf(SpiegLogF,"-d: DVD Speed Limit: %d Kb/Sec\n",*DvdSpeedP);
	fprintf(SpiegLogF,"-p: Real-Time Priority: %d\n",RtParm.sched_priority);
	return;
NOREALTIME:printf("Real-Time FIFO Scheduling Not Set %s\n",Here);goto STOP;
BADOPENFIL:printf("Failed Open File %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Handle Command Line Options */

void STARTUP(void) {
	const char *Here="@STARTUP";
	int Kyn;
	char DevAtapi[16];
/* Open Device File */
	printf("DISK NEEDED NOW\n");
	Kyn=GETYN("\tProceed? [%c]\n",'Y');
	if(Kyn=='N')goto QUIT;
	strncpy(DevAtapi,"/dev/",16);
	GETSTR("\tSPECIFY DEVICE IN /dev/ [%s]\n",&DevAtapi[5],DEFLTDEV,11);
	GDevFil=open(DevAtapi,O_RDONLY);
	if(GDevFil==-1)goto BADDEVOPEN;
/* Start Unit */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x1B; /* START STOP UNIT */
	GRequest.Cmd[4]=1; /* No-Load Start */
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"START STOP UNIT",&GRequest)!=0)goto BADSTART;
/* Inquiry Data */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x12; /* INQUIRY */
	GRequest.Cmd[4]=96; /* Allocation Length */
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"INQUIRY",&GRequest)!=0)goto BADINQ;
	SHOWINQUIRY();
/* Test Unit Ready */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x00; /* TEST UNIT READY */
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"TEST UNIT READY",&GRequest)!=0) goto BADTEST;
	printf("DEVICE FILE OPENED, UNIT STARTED, UNIT TESTED READY\n");
	return;
QUIT:printf("Quit To Insert Media %s\n",Here);goto STOP;
BADDEVOPEN:printf("Open Device File Failed %s\n",Here);goto STOP;
BADSTART:printf("Start Unit Failed %s\n",Here);goto STOP;
BADINQ:printf("Failed Inquiry %s\n",Here);goto STOP;
BADTEST:printf("Failed Test Unit Ready %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Unit Startup */

uint8_t CONFIGEVAL(void) {
	const char *Here="@CONFIGEVAL";
	int i,j,DescN;
	uint8_t WriteMode,AddLength,Active;
	uint16_t Jshort,CurProfl,ProflN,FeatrN;
/* Display Individual Configuration Features */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x46; /* GET CONFIGURATION */
	GRequest.Cmd[1]=2; /* Single Feature Mode */
	Jshort=0;
	BIGEND2(&GRequest.Cmd[2],&Jshort); /* Profile List */
	Jshort=sizeof(GTransBuf);
	BIGEND2(&GRequest.Cmd[7],&Jshort); /* Allocation Length */
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"GET CONFIGURATION: Profile List",&GRequest)!=0)goto BADCONFIG;
	fprintf(SpiegLogF,"\nGET CONFIGURATION: Profile List\n");
/* Header Adds Eight Bytes To Profile List */
	BIGEND2(&CurProfl,GTransBuf+6);
	BIGEND2(&FeatrN,GTransBuf+8);
	if(FeatrN!=0)goto PROFLERR;
	printf("Current Profile: %04Xh, %s\n",CurProfl,PROFILES(CurProfl,&WriteMode));
	AddLength=(uint8_t)GTransBuf[11];
	DescN=AddLength>>2;
	fprintf(SpiegLogF,"Profile List Entries: %d, Active [Y] & Inactive [N]\n",DescN);
	for(i=0;i<DescN;i++) {
		j=(i<<2)+12;
		BIGEND2(&ProflN,GTransBuf+j);
		Active=(GTransBuf[j+2]&1)?'Y':'N';
		fprintf(SpiegLogF,"%04Xh [%c] %s\n",ProflN,Active,PROFILES(ProflN,&WriteMode));
	} /* Profile List */
	fprintf(SpiegLogF,"Current Profile: %04Xh, %s\n",CurProfl,PROFILES(CurProfl,&WriteMode));
	if(WriteMode==0xFF)goto WRONGMEDIUM;
	return WriteMode; /* CDR/RW Returns 0x01 */
BADCONFIG:printf("Failed Configuration Request %s\n",Here);goto STOP;
PROFLERR:printf("Wrong Feature Number For Profile List %s\n",Here);goto STOP;
WRONGMEDIUM:printf("Unsuitable Medium Or Drive Identified %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Hardware Configuration */

const char *PROFILES(uint16_t Code,uint8_t *WriteModeP) {
	*WriteModeP=0xFF; /* Default For Unsuitable Medium */
	switch(Code) {
		case 0x0001:
		return "Non-Removable Re-Writable Disk";
		case 0x0002:
		return "Removable Re-Writable Disk";
		case 0x0003:
		return "Magneto-Optical Sector-Erasable";
		case 0x0004:
		return "Optical Write-Once";
		case 0x0005:
		return "Advance Storage Magneto-Optical";
		case 0x0008:
		return "Read-Only CD-ROM";
		case 0x0009:
		*WriteModeP=0x01;
		return "Write-Once CD-R";
		case 0x000A:
		*WriteModeP=0x01;
		return "Re-Writable CD-RW";
		case 0x0010:
		return "Read-Only DVD-ROM";
		case 0x0011:
		*WriteModeP=0x20;
		return "DVD-R Write-Once Using Sequential Recording";
		case 0x0012:
		return "Re-Writable DVD-RAM";
		case 0x0013:
		return "DVD-RW Re-Writable Using Restricted Over-Write";
		case 0x0014:
		*WriteModeP=0x20;
		return "DVD-RW Re-Writable Using Sequential Recording";
		case 0x0015:
		return "Dual-Layer DVD-R Using Sequential Recording";
		case 0x0016:
		return "Dual-Layer DVD-R Using Layer-Jump Recording";
		case 0x0017:
		return "Dual-Layer DVD-RW Re-Writable";
		case 0x0018:
		return "DVD-Download Disk Recording";
		case 0x001A:
		return "DVD+RW Re-Writable";
		case 0x001B:
		*WriteModeP=0x48; /* Write Parms Unused, Under-Run Protected */
		return "DVD+R Recordable";
		case 0x0020:
		return "Read-Only Double Density DDCD-ROM";
		case 0x0021:
		return "Write-Once Double Density DDCD-R";
		case 0x0022:
		return "Re-Writable Double Density DDCD-RW";
		case 0x002A:
		return "DVD+RW Re-Writable Dual Layer";
		case 0x002B:
		return "DVD+R Recordable Dual Layer";
		case 0x0040:
		return "Blu-Ray BD-ROM";
		case 0x0041:
		return "Blu-Ray BD-R Recordable In Sequential Recording Mode";
		case 0x0042:
		return "Blu-Ray BD-R Recordable In Random Recording Mode";
		case 0x0043:
		return "Blu-Ray BD-RE Re-Writable";
		case 0x0050:
		return "Read-Only HD DVD-ROM";
		case 0x0051:
		return "Write-Once HD DVD-R";
		case 0x0052:
		return "Re-Writable HD DVD-RAM";
		case 0x0053:
		return "Re-Writable HD DVD-RW";
		case 0x0058:
		return "Dual-Layer HD DVD-R Write-Once";
		case 0x005A:
		return "Dual-Layer HD DVD-RW Re-Writable";
		case 0xFFFF:
		return "Non-Standard Profile";
		default:
		return "Reserved Profile Number";
	}
} /* Profile Numbers */

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* iface.c - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

#define IFACE
#include "spiegel.h"
#undef IFACE

int GDevFil; /* Device File */
int GBufFac; /* Write Buffer Ring Factor */
off_t GIsoSize; /* Iso File Size Bytes */
uint32_t GDevCache; /* CDRW Hardware Buffer Size */
uint32_t GSeekLBA; /* Last Valid Seek Sector */
ROOTPATH GIsoFilPath; /* UFS Root Path */
SECTOR GTransBuf; /* Global Sector Buffer */
PARMSTOR GWritePage; /* Write Page Spec */
FILE *SpiegLogF; /* Spiegel Log File */
MMCREQ GRequest; /* Linux ATAPI Request */

void SYSEVAL(int argc,char **argv) {
	int J;
	char TestChar;
	struct sched_param RtParm;
	fprintf(SpiegLogF,"\nENVIRONMENT\n");
	J=sizeof(int);
	fprintf(SpiegLogF,"sizeof(int): %d\n",J);
	J=sizeof(off_t);
	fprintf(SpiegLogF,"sizeof(off_t): %d\n",J);
	TestChar=0xFF;
	fprintf(SpiegLogF,"0xFF char Value: %d\n",TestChar);
	fprintf(SpiegLogF,"Program Name: %s\n",argv[0]);
	fprintf(SpiegLogF,"Command Line Arguments: %d\n",argc);
	fprintf(SpiegLogF,"\nREALTIME SCHEDULING PRIORITY\n");
	J=sched_getscheduler(0);
	fprintf(SpiegLogF,"Scheduling Policy: %s\n",SCHEDPOLICY(J));
	J=sched_getparam(0,&RtParm);
	fprintf(SpiegLogF,"Realtime Scheduling Value: %d\n",RtParm.sched_priority);
	return;
} /* Misc System Data */

const char *SCHEDPOLICY(int J) {
	const char *Here="@SCHEDPOLICY";
	switch(J) {
		case SCHED_OTHER:
		printf("WARNING: Non-Root User: No Enhanced Priority\n");
		return "Standard Round-Robin Time-Sharing";
		case SCHED_FIFO:
		return "Pre-Emptive Real-Time First-In First-Out";
		case SCHED_RR:
		return "Real-Time Round-Robin";
		default:
		goto NOPRIORITY;
	} /* Real-Time Categories */
NOPRIORITY:printf("Unknown Scheduling Priority %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Scheduling Policies */

int DOPACKET(int DevFil,const char *Desc,MMCREQ *RequestP) {
	uint8_t SenseKey,SenseBuf[16];
	sg_io_hdr_t SgReq;
/* FreeBSD Uses struct ata_ioc_request & IOCATAREQUEST ioctl */
	memset(&SgReq,0,sizeof(SgReq));
	SgReq.interface_id='S';
	SgReq.dxfer_direction=RequestP->XferDirn;
	SgReq.cmd_len=12; /* Using ATAPI Commands */
	SgReq.mx_sb_len=sizeof(SenseBuf);
	SgReq.dxfer_len=RequestP->XferLen;
	SgReq.dxferp=RequestP->XferP;
	SgReq.cmdp=RequestP->Cmd;
	SgReq.sbp=SenseBuf;
	if(ioctl(DevFil,SG_IO,&SgReq)<0)return -2;
	if(SgReq.masked_status==0)return 0;
	fprintf(SpiegLogF,"\n%s: Masked Status: %d\n",Desc,SgReq.masked_status);
	fprintf(SpiegLogF,"errno Says: %s\n",strerror(errno));
/* SCSI Sense Codes */
	SenseKey=SenseBuf[2]&0x0F;
	fprintf(SpiegLogF,"SCSI Sense Key: %02Xh, %s\n",SenseKey,SENSEDESCS(SenseKey));
	fprintf(SpiegLogF,"Additional Sense Code ASC: %02Xh\n",SenseBuf[12]);
	fprintf(SpiegLogF,"Sense Code Qualifier ASCQ: %02Xh\n",SenseBuf[13]);
	fflush(NULL); /* Flush All Buffered Output */
	return SgReq.masked_status;
} /* PACKET ioctl For SCSI Command */

const char *SENSEDESCS(uint8_t SenseKey) {
	switch(SenseKey) {
		case 0x00:
		return "No Specific Sense Key Information";
		case 0x01:
		return "Last Command Complete, Recovered From Error";
		case 0x02:
		return "Logical Unit Not Ready, Cannot Be Accessed";
		case 0x03:
		return "Non-Recovered Medium Error";
		case 0x04:
		return "Non-Recoverable Hardware Failure";
		case 0x05:
		return "Illegal Request, Illegal Data Parameter";
		case 0x06:
		return "Unit Attention, Medium Changed Or Target Reset";
		case 0x07:
		return "Data Protected, Read or Write Failed";
		case 0x08:
		return "Encountered Blank Medium, End Of Formatted Data";
		case 0x09:
		return "Vendor Specific Sense Key";
		case 0x0A:
		return "Copy Command Aborted";
		case 0x0B:
		return "Target Aborted Command, Should Retry";
		case 0x0C:
		return "Search Found Equal Comparison";
		case 0x0D:
		return "Data Overflowed End Of Partition";
		case 0x0E:
		return "Source Data Miscompared Against Medium";
		case 0x0F:
		return "Reserved Sense Key";
		default:
		return "See SPC Sense Keys";
	}
} /* Sense Keys */

void SHOWINQUIRY(void) {
	uint8_t Jbyte;
	fprintf(SpiegLogF,"\nINQUIRY\n");
	Jbyte=((uint8_t)GTransBuf[0]&0xE0)>>5;
	fprintf(SpiegLogF,"Peripheral Qualifier: %u, %s\n",Jbyte,PERIPHQUALS(Jbyte));
	Jbyte=(uint8_t)GTransBuf[0]&31;
	fprintf(SpiegLogF,"Device Type: %02Xh, %s\n",Jbyte,DEVTYPES(Jbyte));
	fprintf(SpiegLogF,"Medium Is Removable: %c\n",(GTransBuf[1]&128)?'Y':'N');
	Jbyte=(uint8_t)GTransBuf[2]&7;
	fprintf(SpiegLogF,"ANSI Version: %s\n",ANSIVERSIONS(Jbyte));
	fprintf(SpiegLogF,"Vendor ID String: %1.8s\n",&GTransBuf[8]);
	fprintf(SpiegLogF,"Product ID String: %1.16s\n",&GTransBuf[16]);
	fprintf(SpiegLogF,"Product Revision: %1.4s\n",&GTransBuf[32]);
	return;
} /* INQUIRY Response */

const char *PERIPHQUALS(uint8_t Code) {
	switch(Code) {
		case 0:
		return "Peripheral Connected, Connection Undetermined";
		case 1:
		return "Peripheral Not Connected, Though Is Supported";
		case 2:
		return "Reserved Peripheral Qualifier";
		case 3:
		return "Device Server Cannot Support A Peripheral Here";
		default:
		return "Vendor Specific Peripheral Qualifier";
	}
} /* Peripheral Qualifiers */

const char *DEVTYPES(uint8_t DevType) {
	switch(DevType) {
		case 0x00:
		return "Direct Access Block Device, Magnetic Disc";
		case 0x01:
		return "Sequential Access Device, Magnetic Tape";
		case 0x02:
		return "Printer Device";
		case 0x03:
		return "Processor Device";
		case 0x04:
		return "Write Once Device, Optical Disc";
		case 0x05:
		return "CD/DVD Device";
		case 0x06:
		return "Scanner Device";
		case 0x07:
		return "Optical Memory Device, Optical Disc";
		case 0x08:
		return "Medium Changer Device, Jukebox";
		case 0x09:
		return "Communications Device";
		case 0x0A: case 0x0B:
		return "ASC IT8 Device, Graphic Arts Pre-Press";
		case 0x0C:
		return "Storage Array Controller, RAID";
		case 0x0D:
		return "Enclosure Services Device";
		case 0x0E:
		return "Simplified Direct Access Device";
		case 0x0F:
		return "Optical Card Reader/Writer";
		case 0x10:
		return "Bridge Controller Commands";
		case 0x11:
		return "Object-Based Storage Device";
		case 0x12:
		return "Automation/Drive Interface";
		case 0x13:
		return "Security Manager Device";
		case 0x1E:
		return "Well Known Logical Unit";
		case 0x1F:
		return "Unknown - No Device Type";
		default:
		return "Reserved Device Type";
	}
} /* Peripheral Device Types */

const char *ANSIVERSIONS(uint8_t Code) {
	switch(Code) {
		case 0x00:
		return "ATAPI Device Not Claiming SCSI Conformance";
		case 0x01:
		return "Device Complies To ANSI X3.131:1986 (SCSI-1)";
		case 0x02:
		return "Device Complies To ANSI X3.131:1994 (SCSI-2)";
		case 0x03:
		return "Device Complies To ANSI INCITS 301-1997 (SPC)";
		case 0x04:
		return "Device Complies To ANSI INCITS 351-2001 (SPC-2)";
		case 0x05:
		return "Device Complies To ANSI INCITS 408-2005 (SPC-3)";
		case 0x06:
		return "Device Complies To SPC-4";
		default:
		return "Future ANSI Standard (?)";
	}
} /* ANSI Versions Implemented */

void DEFLTWRITE(void) {
	const char *Here="@DEFLTWRITE";
	uint16_t Jshort;
/* MODE SENSE Default Write Parameters Mode Page */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5A; /* MODE SENSE */
	GRequest.Cmd[1]=8; /* Disable Block Descriptors */
	GRequest.Cmd[2]=0x85; /* Default Write Params */
	Jshort=64; /* Global Write Page Size */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GWritePage; /* Global Write Page */
	GRequest.XferLen=64; /* Global Write Page Size */
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"MODE SENSE Default Write Parameters",&GRequest)!=0)goto ERRSENSWR;
/* Adjust Write Parameters Page */
	memset(GWritePage,0,8); /* Mode Header Reserved Or Obsolete */
	GWritePage[8]&=0x3F; /* Write Params Page Code */
	GWritePage[10]&=0x6F; /* Ensure Zero Test Write Bit */
/* MODE SELECT Clears Test Track Data */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x55; /* MODE SELECT */
	GRequest.Cmd[1]=0x10; /* Standard Page Format */
	Jshort=GWritePage[9]+10; /* Header Plus Total Page Bytes */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GWritePage; /* Global Write Page */
	GRequest.XferLen=64; /* Global Write Page Size */
	GRequest.XferDirn=SG_DXFER_TO_DEV;
	if(DOPACKET(GDevFil,"MODE SELECT Write Parameters",&GRequest)!=0)goto ERRSELWR;
	return;
ERRSENSWR:printf("Error Sensing Write Params %s\n",Here);goto STOP;
ERRSELWR:printf("Error Selecting Write Params %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Reset To Default Write Page */

const char *NOTIFICATIONS(uint8_t Code) {
	switch(Code) {
		case 1:
		return "Operational State Change";
		case 2:
		return "Power Management Event";
		case 3:
		return "External Request";
		case 4:
		return "Media Status Event";
		case 5:
		return "Multiple Initiator Control";
		case 6:
		return "Device Busy Event";
		default:
		return "Reserved Event/Status Notification";
	}
} /* Event Status Notification Types */

void BIGEND2(void *Out16,void *In16) {
	uint8_t *OutByteP,*InByteP;
	OutByteP=(uint8_t*)Out16;
	InByteP=(uint8_t*)In16;
	*OutByteP=*(InByteP+1);
	*(OutByteP+1)=*InByteP;
	return;
} /* Reverse 2 Bytes To Other Endian */

void BIGEND4(void *Out32,void *In32) {
	uint8_t *OutByteP,*InByteP;
	OutByteP=(uint8_t*)Out32;
	InByteP=(uint8_t*)In32;
	*OutByteP=*(InByteP+3);
	*(OutByteP+1)=*(InByteP+2);
	*(OutByteP+2)=*(InByteP+1);
	*(OutByteP+3)=*InByteP;
	return;
} /* Reverse 4 Bytes To Other Endian */

void SPLITEND2(void *Out16,void *HiByte,void *LoByte) {
	uint8_t *OutByteP,*HiByteP,*LoByteP;
	OutByteP=(uint8_t*)Out16;
	HiByteP=(uint8_t*)HiByte;
	LoByteP=(uint8_t*)LoByte;
	*OutByteP=*LoByteP;
	*(OutByteP+1)=*HiByteP;
	return;
} /* Assemble 2 Byte Integer */

int GETYN(const char *Prompt,char DefaultVal) {
	char Buf[80];
	int Clen,Ryn;
	do {
		printf(Prompt,DefaultVal);
		memset(Buf,0,80);
		fgets(Buf,80,stdin);
		if(strcmp(Buf,"\n")==0)return toupper(DefaultVal);
		Clen=strlen(Buf);
		Ryn=toupper(Buf[Clen-2]);
	} while(Ryn!='Y'&&Ryn!='N');
	return Ryn;
} /* Get Y/N Response To Prompt */

int GETINT(const char *Prompt,int DefaultVal) {
	char Buf[80];
	int ScanVal,Jret;
	do {
		printf(Prompt,DefaultVal);
		memset(Buf,0,80);
		fgets(Buf,80,stdin);
		if(strcmp(Buf,"\n")==0)return DefaultVal;
		ScanVal=sscanf(Buf," %d",&Jret);
	} while(ScanVal!=1||ScanVal==EOF);
	return Jret;
} /* Get Integer Response To Prompt */

void GETSTR(const char *Prompt,char *RetStr,const char *Default,size_t ResLen) {
	char *GetStr;
	ssize_t GotLen;
	printf(Prompt,Default);
	memset(RetStr,0,ResLen);
	GetStr=calloc(ResLen,1);
	GotLen=getline(&GetStr,&ResLen,stdin);
	if(GetStr[GotLen-1]=='\n')GotLen--;
	GetStr[GotLen]=0;
	if(GetStr[0]!=0)strncpy(RetStr,GetStr,ResLen);
	else strncpy(RetStr,Default,ResLen);
	free(GetStr);
	return;
} /* Get String Response To Prompt */

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* cddev.c - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

#include "spiegel.h"

void DRIVEEVAL(void) {
	const char *Here="@DRIVEEVAL";
	uint8_t Jbyte;
	uint16_t Jshort,BufCache;
	Jshort=sizeof(GTransBuf);
/* Mode Sense CD Capabilities & Mechanical Status Page */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5A; /* MODE SENSE */
	GRequest.Cmd[1]=8; /* Disable Block Descriptors */
	GRequest.Cmd[2]=0x2A; /* Current CD Capabilities Page */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"MODE SENSE CD Capabilities",&GRequest)!=0)goto HARDERR;
	SHOWCAPABILITIES(&BufCache);
	GDevCache=BufCache<<10; /* Device Cache Size Bytes */
	fprintf(SpiegLogF,"\nCDRW Hardware Buffer: %u Bytes\n",GDevCache);
/* Mode Sense Write Parameters Mode Page */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5A; /* MODE SENSE */
	GRequest.Cmd[1]=8; /* Disable Block Descriptors */
	GRequest.Cmd[2]=0x05; /* Current Write Params Mode Page */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"MODE SENSE Current Write Parameters",&GRequest)!=0)goto HARDERR;
	SHOWWRITEPARAMS("Initial Drive Settings");
/* Write Page Defaults Clear Firmware Data Using Zero Test Write Bit */
	DEFLTWRITE();
/* Get Event/Status Notification */
	fprintf(SpiegLogF,"\nGET EVENT/STATUS NOTIFICATION\n");
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x4A; /* GET EVENT/STATUS NOTIFICATION */
	GRequest.Cmd[1]=1; /* Immed/Polled */
	GRequest.Cmd[4]=0x54; /* Busy + Media + Power Classes */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"GET EVENT/STATUS NOTIFICATION",&GRequest)!=0)goto BADNOTIFGET;
	Jbyte=GTransBuf[2]&0x07;
	fprintf(SpiegLogF,"Notification Returned: %u, %s\n",Jbyte,NOTIFICATIONS(Jbyte));
	if(!(GTransBuf[3]&0x40))goto NOBUSYPOLL;
	return;
HARDERR:printf("Failed Hardware Request %s\n",Here);goto STOP;
BADNOTIFGET:printf("Failed Event/Status Notification Request %s\n",Here);goto STOP;
NOBUSYPOLL:printf("Cannot Poll For Device Busy Events %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Hardware Parameter Pages */

void SHOWCAPABILITIES(uint16_t *BufCacheP) {
	const char *Here="@SHOWCAPABILITIES";
	uint8_t Jbyte,CapsFound;
	char *RotCtl;
	uint16_t Jshort,StartByte,WriteSpeed;
	int j;
	fprintf(SpiegLogF,"\nCAPABILITIES & MECHANICAL STATUS\n");
/* Header Adds Eight Bytes To Mode Page Locations */
	j=(uint8_t)GTransBuf[9]+10;
	memset(&GTransBuf[j],0,2048-j);
	fprintf(SpiegLogF,"Reads CD-R: %c\n",(GTransBuf[10]&1)?'Y':'N');
	fprintf(SpiegLogF,"Reads CD-RW: %c\n",(GTransBuf[10]&2)?'Y':'N');
	fprintf(SpiegLogF,"Reads Method 2 Fixed Packet Addressing: %c\n",(GTransBuf[10]&4)?'Y':'N');
	fprintf(SpiegLogF,"Reads DVD-ROM: %c\n",(GTransBuf[10]&8)?'Y':'N');
	fprintf(SpiegLogF,"Reads DVD-R: %c\n",(GTransBuf[10]&16)?'Y':'N');
	fprintf(SpiegLogF,"Reads DVD-RAM: %c\n",(GTransBuf[10]&32)?'Y':'N');
	fprintf(SpiegLogF,"Writes CD-R: %c\n",(GTransBuf[11]&1)?'Y':'N');
	fprintf(SpiegLogF,"Writes CD-RW: %c\n",(GTransBuf[11]&2)?'Y':'N');
	fprintf(SpiegLogF,"Test Write Function Supported: %c\n",(GTransBuf[11]&4)?'Y':'N');
	CapsFound=((uint8_t)GTransBuf[11]&7); /* CD-R/RW Write & Test Capability */
	fprintf(SpiegLogF,"Writes DVD-R: %c\n",(GTransBuf[11]&16)?'Y':'N');
	fprintf(SpiegLogF,"Writes DVD-RAM: %c\n",(GTransBuf[11]&32)?'Y':'N');
	fprintf(SpiegLogF,"Reads Mode 2 Form 1 CD Sectors: %c\n",(GTransBuf[12]&16)?'Y':'N');
	fprintf(SpiegLogF,"Reads Mode 2 Form 2 CD Sectors: %c\n",(GTransBuf[12]&32)?'Y':'N');
	fprintf(SpiegLogF,"Reads Multi-Session CD Disks: %c\n",(GTransBuf[12]&64)?'Y':'N');
	fprintf(SpiegLogF,"Supports Buffer Under-Run Free CDR/RW Recording: %c\n",(GTransBuf[12]&128)?'Y':'N');
	CapsFound|=((uint8_t)GTransBuf[12]&0xC0); /* Under-Run Prot & Multi-Session Capability */
	fprintf(SpiegLogF,"Reads Raw R-W Sub-Channel From Lead-In: %c\n",(GTransBuf[15]&32)?'Y':'N');
	BIGEND2(&Jshort,GTransBuf+16);
	fprintf(SpiegLogF,"Maximum Read Speed (MMC-1) KB/sec: %hu\n",Jshort);
	BIGEND2(BufCacheP,GTransBuf+20);
	fprintf(SpiegLogF,"Buffer Cache Size KB: %hu\n",*BufCacheP);
	BIGEND2(&Jshort,GTransBuf+22);
	fprintf(SpiegLogF,"Current Read Speed (MMC-1) KB/sec: %hu\n",Jshort);
	BIGEND2(&Jshort,GTransBuf+26);
	fprintf(SpiegLogF,"Maximum Write Speed (MMC-1) KB/sec: %hu\n",Jshort);
	BIGEND2(&Jshort,GTransBuf+28);
	fprintf(SpiegLogF,"Current Write Speed (MMC-1) KB/sec: %hu\n",Jshort);
	fprintf(SpiegLogF,"Pure CAV Rotation Control Selected: %c\n",(GTransBuf[35]&0x03)?'Y':'N');
	BIGEND2(&Jshort,GTransBuf+36);
	fprintf(SpiegLogF,"Current Write Speed (MMC-3) KB/sec: %hu\n",Jshort);
	BIGEND2(&Jshort,GTransBuf+38);
	fprintf(SpiegLogF,"Number Of Write Speed Performance Descriptors: %hu\n",Jshort);
	for(j=1;j<=Jshort;j++) {
		StartByte=40+((j-1)<<2);
		Jbyte=(uint8_t)GTransBuf[StartByte+1]&7;
		switch(Jbyte) {
			case 0:RotCtl="CLV/Part-CAV/Default";break;
			case 1:RotCtl="Pure CAV";break;
			default:RotCtl="Rotation Control Undefined";
		} /* Rotation Control Types */
		BIGEND2(&WriteSpeed,GTransBuf+StartByte+2);
		fprintf(SpiegLogF,"Write Speed KB/sec: %hu, %s\n",WriteSpeed,RotCtl);
	} /* List Write Speed Performance Descriptors */
	if(CapsFound<0xC5)goto NOCAP;
	return;
NOCAP:printf("Drive Lacks Essential Capability %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* CD Capabilities & Mechanical Status Page */

void SHOWWRITEPARAMS(const char *Desc) {
	uint8_t Jbyte;
	uint32_t Jlong;
	int j;
	fprintf(SpiegLogF,"\nCURRENT WRITE PARAMETERS: %s\n",Desc);
/* Header Adds Eight Bytes To Mode Page Locations */
	j=(uint8_t)GTransBuf[9]+10;
	memset(&GTransBuf[j],0,2048-j);
	Jbyte=(uint8_t)GTransBuf[10]&0x0F;
	fprintf(SpiegLogF,"Write Stream Type: %02Xh, %s\n",Jbyte,WRITETYPES(Jbyte));
	fprintf(SpiegLogF,"Test Writing Enabled: %c\n",(GTransBuf[10]&16)?'Y':'N');
	fprintf(SpiegLogF,"Link Size Valid: %c\n",(GTransBuf[10]&32)?'Y':'N');
	fprintf(SpiegLogF,"Buffer Underrun Protection Enabled: %c\n",(GTransBuf[10]&64)?'Y':'N');
	Jbyte=(uint8_t)GTransBuf[11]&0x0F; /* Bit 1 Means Copy Permitted */
	fprintf(SpiegLogF,"Track Mode Control Nibble: %02Xh, %s\n",Jbyte,TRACKMODES(Jbyte));
	fprintf(SpiegLogF,"Fixed Packet Type: %c\n",(GTransBuf[11]&32)?'Y':'N');
	Jbyte=((uint8_t)GTransBuf[11]&0xC0)>>6;
	fprintf(SpiegLogF,"Multisession Closure: %u, %s\n",Jbyte,MULTISESSCLOSES(Jbyte));
	Jbyte=(uint8_t)GTransBuf[12]&0x0F;
	fprintf(SpiegLogF,"Data Block Type: %u, %s\n",Jbyte,DATABLOCKTYPES(Jbyte));
	fprintf(SpiegLogF,"Linking Loss Area Size: %u\n",(uint8_t)GTransBuf[13]);
	Jbyte=(uint8_t)GTransBuf[16];
	fprintf(SpiegLogF,"Session Format: %02Xh, %s\n",Jbyte,SESSIONFORMATS(Jbyte));
	BIGEND4(&Jlong,GTransBuf+18);
	fprintf(SpiegLogF,"Fixed Packet Size, Blocks: %d\n",Jlong);
	fprintf(SpiegLogF,"Sub-Header Hex:");
	for(j=56;j<60;j++)fprintf(SpiegLogF," %02X",(uint8_t)GTransBuf[j]);
	fprintf(SpiegLogF,"\n");
	return;
} /* Write Parameters Mode Page */

const char *WRITETYPES(uint8_t Code) {
	switch(Code) {
		case 0x00:
		return "Packet/Incremental";
		case 0x01:
		return "Track-At-Once";
		case 0x02:
		return "Session-At-Once";
		case 0x03:
		return "Raw";
		default:
		return "Reserved Write Type";
	}
} /* Write Types */

const char *TRACKMODES(uint8_t Code) {
	switch(Code) {
		case 0x00:
		return "Two Audio Channels No Pre-Emphasis";
		case 0x01:
		return "Two Audio Channels With Pre-Emphasis";
		case 0x02:
		return "Two Audio Channels No Pre-Emphasis";
		case 0x03:
		return "Two Audio Channels With Pre-Emphasis";
		case 0x04:
		return "Data Track Uninterrupted";
		case 0x05:
		return "Data Track Incremental";
		case 0x06:
		return "Data Track Uninterrupted";
		case 0x07:
		return "Data Track Incremental";
		case 0x08:
		return "Four Audio Channels No Pre-Emphasis";
		case 0x09:
		return "Four Audio Channels With Pre-Emphasis";
		case 0x0A:
		return "Four Audio Channels No Pre-Emphasis";
		case 0x0B:
		return "Four Audio Channels With Pre-Emphasis";
		default:
		return "Reserved Track Mode";
	}
} /* Track Modes */

const char *MULTISESSCLOSES(uint8_t Code) {
	switch(Code) {
		case 0:
		return "No B0 Pointer, Next Session Not Allowed";
		case 1:
		return "B0 Pointer = FF:FF:FF, Next Session Not Allowed";
		case 3:
		return "Next Session Allowed, B0 Pointer = Next Program Area";
		default:
		return "Reserved Multisession Field";
	}
} /* Multisession Closures */

const char *DATABLOCKTYPES(uint8_t Code) {
	switch(Code) {
		case 0:
		return "Raw Data *2352, Not Valid For Packet";
		case 1:
		return "Raw Data *2368 With P&Q Sub-Channel";
		case 2:
		return "Raw Data *2448 With P-W Sub-Channel";
		case 3:
		return "Raw Data *2448 With Raw P-W Sub-Channel";
		case 7:
		return "Vendor Specific Data Block Type";
		case 8:
		return "Mode 1 *2048 ISO/IEC 10149 (Mandatory)";
		case 9:
		return "Mode 2 *2336 ISO/IEC 10149";
		case 10:
		return "Mode 2 *2048 CD-ROM XA, Form 1 (Mandatory)";
		case 11:
		return "Mode 2 *2056 CD-ROM XA, Form 1, With Sub-header";
		case 12:
		return "Mode 2 *2324 CD-ROM XA, Form 2";
		case 13:
		return "Mode 2 *2332 CD-ROM XA, Form 1, Form 2, Mixed (Mandatory)";
		case 15:
		return "Vendor Specific Data Block Type";
		default:
		return "Reserved Data Block Type";
	}
} /* Data Block Types */

const char *SESSIONFORMATS(uint8_t Code) {
	switch(Code) {
		case 0x00:
		return "CD-DA Or CD-ROM Type Data Disc";
		case 0x10:
		return "CD-I Disc";
		case 0x20:
		return "CD-ROM XA Or DDCD Disc";
		default:
		return "Reserved Session Format Code";
	}
} /* Session Format Types */

int DISCEVAL(uint16_t *FirstTrackP,uint16_t *LastTrackP,uint16_t *SessTotP) {
	const char *Here="@DISCEVAL";
	int DiscStatus;
	uint16_t Jshort;
	uint32_t Capacity,BlockLen;
	float MegUsed;
/* Read CD Recorded Capacity */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x25; /* READ CD RECORDED CAPACITY */
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"READ CAPACITY",&GRequest)!=0)goto MEDIAERR;
	fprintf(SpiegLogF,"\nREAD CAPACITY\n");
	BIGEND4(&Capacity,&GTransBuf[0]);
	fprintf(SpiegLogF,"Last Valid Seek LBA: %d\n",Capacity);
	GSeekLBA=Capacity;
	BIGEND4(&BlockLen,&GTransBuf[4]);
	fprintf(SpiegLogF,"Block Length: %d\n",BlockLen);
	if(BlockLen!=2048)goto BLOCKWRONG;
	MegUsed=(float)Capacity/512.0;
	printf("Capacity Already Used: %3.1f Meg\n",MegUsed);
/* Read Disc Information */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x51; /* READ DISC INFORMATION */
	Jshort=34;
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"READ DISC INFORMATION",&GRequest)!=0)goto MEDIAERR;
	DiscStatus=DISCINFO(FirstTrackP,LastTrackP,SessTotP);
	return DiscStatus;
BLOCKWRONG:printf("Wrong Block Length: 2048 Needed On Data CDs %s\n",Here);goto STOP;
MEDIAERR:printf("Failed Media Request %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Disc Evaluation */

int DISCINFO(uint16_t *FirstTrackP,uint16_t *LastTrackP,uint16_t *SessTotP) {
	const char *Here="@DISCINFO";
	uint8_t Jbyte;
	uint16_t Jshort,SessTrackN;
	uint32_t Jlong;
	int DiscType,LastSessState,DiscStatus;
	fprintf(SpiegLogF,"\nREAD DISC INFORMATION\n");
	BIGEND2(&Jshort,GTransBuf);
	memset(&GTransBuf[Jshort+2],0,2046-Jshort);
	Jbyte=(uint8_t)GTransBuf[2]&0x03;
	fprintf(SpiegLogF,"Disc Status: %u, %s\n",Jbyte,DISCSTATUSS(Jbyte));
	DiscStatus=Jbyte;
	Jbyte=((uint8_t)GTransBuf[2]&0x0C)>>2;
	fprintf(SpiegLogF,"Last Session State: %u, %s\n",Jbyte,LASTSESSIONS(Jbyte));
	LastSessState=Jbyte;
	fprintf(SpiegLogF,"Erasable RW Medium: %c\n",(GTransBuf[2]&16)?'Y':'N');
	fprintf(SpiegLogF,"First Track Number: %u\n",(uint8_t)GTransBuf[3]);
	*FirstTrackP=(uint16_t)GTransBuf[3];
	SPLITEND2(&Jshort,&GTransBuf[9],&GTransBuf[4]);
	fprintf(SpiegLogF,"Number Of Sessions: %hu\n",Jshort);
	*SessTotP=Jshort;
	SPLITEND2(&Jshort,&GTransBuf[10],&GTransBuf[5]);
	fprintf(SpiegLogF,"First Track In Last Session: %hu\n",Jshort);
	SessTrackN=Jshort;
	SPLITEND2(&Jshort,&GTransBuf[11],&GTransBuf[6]);
	fprintf(SpiegLogF,"Last Track In Last Session: %hu\n",Jshort);
	*LastTrackP=Jshort;
	Jbyte=(uint8_t)(GTransBuf[8]);
	fprintf(SpiegLogF,"Disc Type: %02Xh, %s\n",Jbyte,DISCTYPES(Jbyte));
	DiscType=(uint8_t)GTransBuf[8];
	BIGEND4(&Jlong,GTransBuf+12);
	fprintf(SpiegLogF,"Disc Identification: %d\n",Jlong);
	fprintf(SpiegLogF,"Number Of Power-Calibrated Speeds: %u\n",(uint8_t)GTransBuf[33]);
/* Cannot Continue With Inappropriate Disc */
	if(DiscStatus>1)goto NOAPPEND;
	if(LastSessState!=0)goto NONEMPTYSESS;
	return DiscStatus;
NOAPPEND:printf("Disc Status Not Empty Or Appendable %s\n",Here);goto STOP;
NONEMPTYSESS:printf("Last Session On Disc Not Empty %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* CDB 0x51, READ DISC INFORMATION */

const char *DISCSTATUSS(uint8_t Code) {
	switch(Code) {
		case 0:
		return "Empty Disc";
		case 1:
		return "Incomplete Disc, Appendable Session";
		case 2:
		return "Finalized, Last Session Closed";
		case 3:
		return "Random Access Only, No Sessions";
		default:
		return "Reserved Disc Status Value";
	}
} /* Disc Statuses */

const char *LASTSESSIONS(uint8_t Code) {
	switch(Code) {
		case 0:
		return "Empty Session";
		case 1:
		return "Incomplete Session";
		case 2:
		return "Damaged Session";
		case 3:
		return "Complete Session";
		default:
		return "Reserved Session State Value";
	}
} /* Last Session States */

const char *DISCTYPES(uint8_t Code) {
	switch(Code) {
		case 0x00:
		return "CD-DA or CD-ROM Disc";
		case 0x10:
		return "CD-I Disc";
		case 0x20:
		return "CD-ROM XA Disc Or DDCD";
		case 0xFF:
		return "Undefined Disc Type";
		default:
		return "Reserved Disc Type";
	}
} /* Disc Types */

uint32_t SESSEVAL(uint32_t *WriteStartP,uint16_t FirstTrackN,uint16_t LastTrackN) {
	const char *Here="@SESSEVAL";
	uint16_t Jshort;
	uint32_t Jlong,FreeBlocks;
/* Read Track Information For All Tracks */
	for(Jlong=FirstTrackN;Jlong<=LastTrackN;Jlong++) {
		memset(&GRequest,0,sizeof(GRequest));
		GRequest.Cmd[0]=0x52; /* READ TRACK INFORMATION */
		GRequest.Cmd[1]=1; /* Specify Track Number */
		BIGEND4(&GRequest.Cmd[2],&Jlong);
		Jshort=40;
		BIGEND2(&GRequest.Cmd[7],&Jshort);
		GRequest.XferP=GTransBuf;
		GRequest.XferLen=sizeof(GTransBuf);
		GRequest.XferDirn=SG_DXFER_FROM_DEV;
		if(DOPACKET(GDevFil,"READ TRACK INFORMATION",&GRequest)!=0)goto MEDIAERR;
		FreeBlocks=TRACKINFO(WriteStartP);
	} /* Writable Disk Returns Data From Invisible Track */
	return FreeBlocks;
MEDIAERR:printf("Failed Media Request %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Sessions Evaluation */

uint32_t TRACKINFO(uint32_t *WriteStartP) {
	uint8_t Jbyte;
	uint16_t Jshort;
	uint32_t Jlong,FreeBlocks;
	fprintf(SpiegLogF,"\nREAD TRACK INFORMATION\n");
	BIGEND2(&Jshort,GTransBuf);
	memset(&GTransBuf[Jshort+2],0,2046-Jshort);
	SPLITEND2(&Jshort,&GTransBuf[32],&GTransBuf[2]);
	fprintf(SpiegLogF,"Track Number: %hu\n",Jshort);
	SPLITEND2(&Jshort,&GTransBuf[33],&GTransBuf[3]);
	fprintf(SpiegLogF,"Session Number: %hu\n",Jshort);
	Jbyte=(uint8_t)GTransBuf[5]&0x0F; /* Bit 1 Means Copy Permitted */
	fprintf(SpiegLogF,"Track Mode Control Nibble: %02Xh, %s\n",Jbyte,TRACKMODES(Jbyte));
	Jbyte=(uint8_t)GTransBuf[6]&0x0F;
	fprintf(SpiegLogF,"Data Mode: %02Xh, %s\n",Jbyte,DATAMODES(Jbyte));
	fprintf(SpiegLogF,"Track Must Be Written With Packets: %c\n",(GTransBuf[6]&32)?'Y':'N');
	fprintf(SpiegLogF,"Track Is Blank: %c\n",(GTransBuf[6]&64)?'Y':'N');
	fprintf(SpiegLogF,"Track Is Reserved: %c\n",(GTransBuf[6]&128)?'Y':'N');
	fprintf(SpiegLogF,"Next Writable Address Valid: %c\n",(GTransBuf[7]&1)?'Y':'N');
	fprintf(SpiegLogF,"Last Recorded Address Valid: %c\n",(GTransBuf[7]&2)?'Y':'N');
	BIGEND4(&Jlong,GTransBuf+8);
	fprintf(SpiegLogF,"Track Start Address: %d\n",Jlong);
	BIGEND4(WriteStartP,GTransBuf+12);
	fprintf(SpiegLogF,"Next Writable Address: %d\n",*WriteStartP);
	BIGEND4(&FreeBlocks,GTransBuf+16);
	fprintf(SpiegLogF,"Free Blocks: %d\n",FreeBlocks);
	BIGEND4(&Jlong,GTransBuf+20);
	fprintf(SpiegLogF,"Fixed Packet Size: %d\n",Jlong);
	BIGEND4(&Jlong,GTransBuf+24);
	fprintf(SpiegLogF,"Track Size: %d\n",Jlong);
	BIGEND4(&Jlong,GTransBuf+28);
	fprintf(SpiegLogF,"Last Recorded Address: %d\n",Jlong);
	BIGEND4(&Jlong,GTransBuf+36);
	fprintf(SpiegLogF,"Read Compatibility LBA: %d\n",Jlong);
	return FreeBlocks;
} /* CDB 0x52, READ TRACK INFORMATION */

const char *DATAMODES(uint8_t Code) {
	switch(Code) {
		case 0x01:
		return "Mode 1 ISO/IEC 10149";
		case 0x02:
		return "Mode 2 ISO/IEC 10149, CD-ROM XA, DDCD";
		case 0x0F:
		return "Data Block Type Unknown, No Track Descriptor Block";
		default:
		return "Reserved Data Mode Type";
	}
} /* Data Modes */

========================================================

/* Copyright 2011 Frank Mitchell - Wildebeest License */
/* writecyc.c - Spiegel v2.0 - Linux Kernel 2.6.16+ */
/* Contact: mitchell@wyatt672earp.force9.co.uk */

#include "spiegel.h"

uint32_t WRITESPEED(uint32_t WriteStart,uint32_t WriteEnd,
  uint32_t WritePerf,uint8_t StreamCtl,const char *Desc) {
	const char *Here="@WRITESPEED";
	int i,j,k,NumDesc,StartByte;
	uint32_t StartLBA,EndLBA,ReadPerf,StartPerf,EndPerf,Jlong;
	uint16_t Jshort;
	uint8_t TypeField[2]={16,20};
	ReadPerf=0x7FFFFFFF; /* Reset Same */
	Jlong=1000; /* Time Millisec */
	Jshort=28; /* Parameter List Length */
	memset(GTransBuf,0,sizeof(GTransBuf));
	GTransBuf[0]=StreamCtl;
	BIGEND4(&GTransBuf[4],&WriteStart);
	BIGEND4(&GTransBuf[8],&WriteEnd);
	BIGEND4(&GTransBuf[12],&ReadPerf);
	BIGEND4(&GTransBuf[16],&Jlong); /* Read Time */
	BIGEND4(&GTransBuf[20],&WritePerf);
	BIGEND4(&GTransBuf[24],&Jlong); /* Write Time */
/* Set Streaming Speed */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0xB6; /* SET STREAMING */
	BIGEND2(&GRequest.Cmd[9],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_TO_DEV;
	if(DOPACKET(GDevFil,"SET STREAMING",&GRequest)!=0)goto ERRSETSPEED;
/* MMC-2 Read & Write Performance Descriptors */
	fprintf(SpiegLogF,"\nSET STREAMING: %s\n",Desc);
	Jshort=120; /* Max Number Descriptors */
	for(i=0;i<2;i++) {
		memset(&GRequest,0,sizeof(GRequest));
		GRequest.Cmd[0]=0xAC; /* GET PERFORMANCE */
		GRequest.Cmd[1]=TypeField[i]; /* Nominal Perfs */
		BIGEND2(&GRequest.Cmd[8],&Jshort);
		GRequest.XferP=GTransBuf;
		GRequest.XferLen=sizeof(GTransBuf);
		GRequest.XferDirn=SG_DXFER_FROM_DEV;
		if(DOPACKET(GDevFil,"GET PERFORMANCE",&GRequest)!=0)goto BADPERFGET;
/* Header Adds Eight Bytes To Performance Descriptor Locations */
		BIGEND4(&Jlong,GTransBuf+0);
		j=Jlong+8;
		memset(&GTransBuf[j],0,2048-j);
		NumDesc=Jlong>>4;
		fprintf(SpiegLogF,"GET PERFORMANCE: %s - Nominal Descriptors: %d\n",
		  (GTransBuf[4]&2)?"Write":"Read",NumDesc);
		for(k=0;k<NumDesc;k++) {
			StartByte=8+(k<<4);
			BIGEND4(&StartLBA,GTransBuf+StartByte);
			BIGEND4(&StartPerf,GTransBuf+StartByte+4);
			BIGEND4(&EndLBA,GTransBuf+StartByte+8);
			BIGEND4(&EndPerf,GTransBuf+StartByte+12);
			fprintf(SpiegLogF,"LBA: %8d - %8d  Speed KB/Sec: %6d - %6d\n",
			  StartLBA,EndLBA,StartPerf,EndPerf);
		} /* List Performance Descriptors */
		if(i==0)ReadPerf=EndPerf;
		else WritePerf=EndPerf;
	} /* Read & Write Descriptors With 10% Tolerance */
	printf("Max Read & Write Speeds KB/Sec Now: %d, %d\n",ReadPerf,WritePerf);
	return WritePerf;
ERRSETSPEED:printf("Error Setting Write Streaming %s\n",Here);goto STOP;
BADPERFGET:printf("Failed Performance Request %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Limit Streaming Speed */

uint8_t WRITEPARMS(uint8_t WriteMode,uint8_t NextSess) {
	const char *Here="@WRITEPARMS";
	int Kyn;
	char *Desc;
	uint16_t Jshort;
	uint32_t Jlong;
/* Global Write Parameters Mode Page */
	if(WriteMode&0x08) {
		fprintf(SpiegLogF,"\nWrite Parameters Mode Page Not Used With DVD+R\n");
		printf("CAUTION: DVD+R ONLY ALLOWS WRITE RUN: NO TESTS\n");
		return WriteMode;
	} /* DVD+R Ignores Write Parameters */
	WriteMode&=0x2F; /* Restore Plain Write Type */
	Kyn=GETYN("\tWill This Be A Test Run? [%c]\n",'Y');
	if(Kyn=='Y') {
		WriteMode|=0x10; /* Test Run */
		Kyn=GETYN("\tUse Under-Run Protection? [%c]\n",'N');
		if(Kyn=='Y')WriteMode|=0x40; /* Under-Run Protected */
	}
	else WriteMode|=0x40; /* Write Run Protected Anyway */
/* Header Adds Eight Bytes To Mode Page Locations */
	memset(GWritePage,0,8); /* Header Reserved Or Obsolete */
	GWritePage[8]&=0x3F; /* Write Params Page Code */
	GWritePage[10]=WriteMode; /* Write Type, Test Condition */
	GWritePage[11]=NextSess; /* Multi-Session, Uninterrupted Track Mode */
	GWritePage[12]=0x08; /* Mode 1 Data Block Type */
	GWritePage[13]=16; /* Choose 32KB Link Size */
	GWritePage[14]=0; /* Reserved */
	GWritePage[15]=0; /* Host Application Code */
	GWritePage[16]=0; /* Mode 1 Data Disk Session Format */
	GWritePage[17]=0; /* Reserved */
        Jlong=16; /* DVD Fixed Packet Size */
	BIGEND4(GWritePage+18,&Jlong);
	memset(GWritePage+24,0,36); /* Media Catalog, ISRC, Sub-Header */
/* MODE SELECT With Test Write Bit, Write Type & Under-Run Protection */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x55; /* MODE SELECT */
	GRequest.Cmd[1]=0x10; /* Standard Page Format */
	Jshort=GWritePage[9]+10; /* Header Plus Total Page Bytes */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GWritePage; /* Global Write Page */
	GRequest.XferLen=64; /* Global Write Page Size */
	GRequest.XferDirn=SG_DXFER_TO_DEV;
	if(DOPACKET(GDevFil,"MODE SELECT Write Parameters",&GRequest)!=0)goto ERRSELWR;
/* MODE SENSE Write Parameters Mode Page */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5A; /* MODE SENSE */
	GRequest.Cmd[1]=8; /* Disable Block Descriptors */
	GRequest.Cmd[2]=0x05; /* Current Write Params Mode Page */
	Jshort=sizeof(GTransBuf);
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"MODE SENSE Current Write Parameters",&GRequest)!=0)goto ERRSENSWR;
	if(WriteMode&0x10)Desc="Test Run Settings";
	else Desc="Write Run Settings";
	SHOWWRITEPARAMS(Desc);
	return WriteMode;
ERRSENSWR:printf("Error Sensing Write Params %s\n",Here);goto STOP;
ERRSELWR:printf("Error Selecting Write Params %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* Set Up Write Parameters Mode Page */

int TESTBURN(uint8_t WriteMode,uint32_t WriteStart,uint16_t SessTrackN,uint8_t CloseFunc) {
	const char *Here="@TESTBURN";
	int RunFail,IsoFildes,WriteK,BufJ,BufRingK,RingBufTot;
	int32_t Jlong,BufSectors,StartSect,BlocksToDo;
	int16_t Jshort;
	off_t *OffSetP;
	void **RingPP;
	if(WriteMode&0x10)fprintf(SpiegLogF,"\nSPIEGEL TEST WRITE\n");
	else fprintf(SpiegLogF,"\nSPIEGEL WRITE RUN\n");
	RunFail=0; /* Write Fail Flag */
	BufSectors=(int32_t)WRITE10MAX>>11;
/* Total Ring Buffer Bytes = Hardware Cache Size /8 *GBufFac */
	RingBufTot=(GDevCache>>3)*GBufFac;
	BufRingK=RingBufTot/(size_t)WRITE10MAX;
	fprintf(SpiegLogF,"Ring Buffer Count: %u\n",BufRingK);
	OffSetP=calloc(BufRingK,sizeof(off_t));
	if(OffSetP==NULL)goto BADCALLOC;
	RingPP=calloc(BufRingK,sizeof(void*));
	if(RingPP==NULL)goto BADCALLOC;
	fflush(NULL); /* Flush All Buffered Output */
	if((IsoFildes=open(GIsoFilPath,O_RDONLY))==-1)goto BADOPENFIL;
	for(BufJ=0;BufJ<BufRingK;BufJ++) {
		RingPP[BufJ]=calloc((size_t)WRITE10MAX,1);
		if(RingPP[BufJ]==NULL)goto BADCALLOC;
		OffSetP[BufJ]=(off_t)WRITE10MAX*BufJ;
		pread(IsoFildes,RingPP[BufJ],(size_t)WRITE10MAX,OffSetP[BufJ]);
	} /* Memory Buffer Ring */
	printf("WAIT UNTIL CLOSE SESSION MESSAGE APPEARS\n");
	BlocksToDo=(GIsoSize+2047)>>11; /* Partial Block? */
	WriteK=0; /* Buffer Serial Count */
	BufJ=0; /* Burn Cycle Index Mod BufRingK */
/* Move To Last Valid Seek Sector */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x2B; /* SEEK(10) */
	BIGEND4(&GRequest.Cmd[2],&GSeekLBA); /* Last Seek LBA */
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"SEEK(10)",&GRequest)!=0)goto SEEKFAIL;
	StartSect=WriteStart;
	while(1) {
		Jshort=(BlocksToDo<BufSectors)?BlocksToDo:BufSectors; /* # Blocks */
		Jlong=(int32_t)Jshort<<11; /* # Bytes + Padding */
		memset(&GRequest,0,sizeof(GRequest));
		GRequest.Cmd[0]=0x2A; /* WRITE(10) */
		BIGEND4(&GRequest.Cmd[2],&StartSect); /* LBA Start */
		BIGEND2(&GRequest.Cmd[7],&Jshort); /* # Blocks */
		GRequest.XferP=RingPP[BufJ];
		GRequest.XferLen=Jlong; /* # Bytes */
		GRequest.XferDirn=SG_DXFER_TO_DEV;
		if(DOPACKET(GDevFil,"WRITE(10)",&GRequest)!=0)goto WRITEFAIL;
		WriteK++;
		StartSect+=Jshort;
		BlocksToDo-=Jshort;
		if(BlocksToDo<=0)goto SYNCHCACHE;
		OffSetP[BufJ]+=RingBufTot;
		pread(IsoFildes,RingPP[BufJ],(size_t)WRITE10MAX,OffSetP[BufJ]);
		BufJ+=(BufJ==(BufRingK-1))?-(BufRingK-1):1; /* Burn Cycle Mod BufRingK */
	} /* Loop FreeBSD ioctl WRITE(10) 64K */
WRITEFAIL: /* Flag Run Failure */
	RunFail=1;
	printf("\nRUN FAILED: WRITE COMMAND ERROR\n");
	fprintf(SpiegLogF,"RUN FAILED: WRITE COMMAND ERROR\n");
	fprintf(SpiegLogF,"errno Says: %s\n",strerror(errno));
SYNCHCACHE: /* Synchronize Cache */
	if(BlocksToDo!=0)fprintf(SpiegLogF,"ERROR! Blocks Discrepancy: %d\n",BlocksToDo);
	fprintf(SpiegLogF,"Buffer Serial Count: %d\n",WriteK);
	fprintf(SpiegLogF,"Final Buffer Block Count: %d\n",Jshort);
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x35; /* SYNCHRONIZE CACHE */
	BIGEND4(&GRequest.Cmd[2],&StartSect); /* Start LBA */
	Jshort=0; /* Range Includes All Remaining Sectors */
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"SYNCHRONIZE CACHE",&GRequest)!=0) {
		printf("Failed SYNCHRONIZE CACHE\n");
		fprintf(SpiegLogF,"Failed SYNCHRONIZE CACHE\n");
	} /* SYNCHRONIZE CACHE Messages */
/* Close Track */
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5B; /* CLOSE TRACK/SESSION */
	GRequest.Cmd[2]=1; /* Close Track */
	Jshort=SessTrackN; /* Close Incomplete Track */
	BIGEND2(&GRequest.Cmd[4],&Jshort);
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"CLOSE TRACK",&GRequest)!=0) {
		printf("Failed CLOSE TRACK\n");
		fprintf(SpiegLogF,"Failed CLOSE TRACK\n");
	} /* CLOSE TRACK Messages */
	if(WriteMode&0x10) {
		printf("Skip CLOSE SESSION In Test Mode\n");
	} /* Test Mode */
	else RunFail=CLOSEWAIT(CloseFunc);
	close(IsoFildes);
	for(BufJ=0;BufJ<BufRingK;BufJ++) {
		free(RingPP[BufJ]);
	} /* Memory Buffer Ring */
	free(RingPP);
	free(OffSetP);
	fprintf(SpiegLogF,"\n");
	return RunFail;
BADCALLOC:printf("Couldn't Allocate Dynamic Memory %s\n",Here);goto STOP;
BADOPENFIL:printf("Couldn't open() Iso File %s\n",Here);goto STOP;
SEEKFAIL:printf("Failed SEEK(10) To Last Valid Seek Sector %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* CDR Writer Driver */

int CLOSEWAIT(uint8_t CloseFunc) {
	const char *Here="@CLOSEWAIT";
	int j=0,RunFail=0;
	uint8_t Jbyte;
	uint16_t Jshort;
	float Time;
/* Close Session */
	printf("WAIT DURING CLOSE SESSION\n");
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x5B; /* CLOSE TRACK/SESSION */
	GRequest.Cmd[1]=1; /* Immed Set */
	GRequest.Cmd[2]=CloseFunc; /* Close Session */
	GRequest.XferDirn=SG_DXFER_NONE;
	if(DOPACKET(GDevFil,"CLOSE SESSION",&GRequest)!=0) {
		RunFail=1;
		printf("Failed CLOSE SESSION\n");
		fprintf(SpiegLogF,"Failed CLOSE SESSION\n");
	} /* CLOSE SESSION Messages */
/* Get Event/Status Notification */
	fprintf(SpiegLogF,"\nGET EVENT/STATUS NOTIFICATION Device Busy Class\n");
	memset(&GRequest,0,sizeof(GRequest));
	GRequest.Cmd[0]=0x4A; /* GET EVENT/STATUS NOTIFICATION */
	GRequest.Cmd[1]=1; /* Immed/Polled */
	GRequest.Cmd[4]=0x40; /* Device Busy Class */
	Jshort=sizeof(GTransBuf);
	BIGEND2(&GRequest.Cmd[7],&Jshort);
	GRequest.XferP=GTransBuf;
	GRequest.XferLen=sizeof(GTransBuf);
	GRequest.XferDirn=SG_DXFER_FROM_DEV;
	if(DOPACKET(GDevFil,"GET EVENT/STATUS NOTIFICATION",&GRequest)!=0)goto BADNOTIFGET;
	Jbyte=GTransBuf[2]&0x07;
	fprintf(SpiegLogF,"Notification Returned: %u, %s\n",Jbyte,NOTIFICATIONS(Jbyte));
	BIGEND2(&Jshort,GTransBuf+6);
	Time=0.1*(float)Jshort;
	printf("Predicted Time Remaining: %5.1f Sec\n",Time);
	j=GTransBuf[5]; /* Device Busy Status */
	while(j!=0) {
		if(usleep(500000)<0)goto BADSLEEP; /* Microsec Sleep */
		if(DOPACKET(GDevFil,"GET EVENT/STATUS NOTIFICATION",&GRequest)!=0)goto BADNOTIFGET;
		j=GTransBuf[5]; /* Device Busy Status */
	} /* Silent Polling */
	if(RunFail==0)printf("Completed CLOSE SESSION\n"); /* Delayed While Polling */
	return RunFail;
BADNOTIFGET:printf("Failed Event/Status Notification Request %s\n",Here);goto STOP;
BADSLEEP:printf("Failed usleep() %s\n",Here);goto STOP;
STOP:printf("errno Says: %s\n",strerror(errno));exit(EXIT_FAILURE);
} /* CLOSE SESSION When Not Test Mode */




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201106151641.08993.mitchell>