#ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE #endif #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 #endif #if defined(__linux) /* ... and "engage" glibc large file support */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #endif #include "transport.hxx" #include #include #define SIGS_BLOCK 1 #define SIGS_UNBLOCK 0 extern "C" void sigs_mask (int); extern "C" void atsignals (void (*)(void)); #define ONEX 1352 // 1385 * 1000 / 1024 static int media_written=0,next_track=1, is_dao=0,quickgrown=0,do_reload=1; static unsigned int stdnap=(256*1000)/ONEX, // measured in milliseconds buf_size=512; // measured in KBs static class __velocity__ { private: int value; public: int operator=(int v) { if (v>=ONEX) stdnap=(buf_size*500)/v; else stdnap=(buf_size*500)/ONEX; return value = v; } operator int() { return value; } } velocity; static void *ioctl_handle=(void *)-1; #ifndef ioctl_fd #define ioctl_fd ((long)ioctl_handle) #endif static unsigned int next_wr_addr=1; // it starts as boolean static unsigned int dao_blocks=0; static unsigned char formats[260],disc_info[32]; extern "C" { extern int dvd_compat,test_write,no_reload,mmc_profile,_argc, wrvfy,no_opc; extern double speed_factor; extern char *ioctl_device,**_argv; } extern "C" int fumount (int fd) { Scsi_Command cmd; return cmd.umount(fd); } extern "C" int media_reload (char *name=NULL,struct stat *sb=NULL) { if (name==NULL) { Scsi_Command cmd(ioctl_handle); pioneer_stop (cmd); #if defined(RELOAD_NEVER_NEEDED) #undef RELOAD_NEVER_NEEDED #define RELOAD_NEVER_NEEDED 1 #else #define RELOAD_NEVER_NEEDED 0 #endif if (RELOAD_NEVER_NEEDED || no_reload>0) { cmd[0] = 0x1E; // ALLOW MEDIA REMOVAL cmd[5] = 0; cmd.transport (); return (errno=0); } #if !RELOAD_NEVER_NEEDED char str[12]; int n; if ((n=fcntl (ioctl_fd,F_GETFD))<0) n=0; fcntl (ioctl_fd,F_SETFD,n&~FD_CLOEXEC); sprintf (str,"%ld",ioctl_fd); execlp(_argv[0],no_reload<0?"-eject":"-reload", str,ioctl_device,(void *)NULL); } else { { Scsi_Command cmd; if (!cmd.associate (name,sb)) return 1; if (cmd.is_reload_needed()) { fprintf (stderr,"%s: reloading tray\n",name); cmd[0] = 0x1E; // ALLOW MEDIA REMOVAL cmd[5] = 0; if (cmd.transport ()) return 1; while (1) // Pioneer DVR-x05 needs this... { cmd[0] = 0x1B; // START/STOP UNIT cmd[1] = 0x1; // "IMMED" cmd[4] = 0x2; // "Eject" cmd[5] = 0; if (cmd.transport() == 0x20407) // "OP IN PROGRESS" { poll (NULL,0,333); continue; } break; } // yes, once again, non-"IMMED"... cmd[0] = 0x1B; // START/STOP UNIT cmd[4] = 0x2; // "Eject" cmd[5] = 0; if (cmd.transport()) return 1; } #if defined(__sun) || defined(sun) else if (volmgt_running()) { setuid(getuid()); execl("/usr/bin/volrmmount","volrmmount","-i",name,(void*)NULL); return 0; // not normally reached } #endif else return 0; // m-m-m-m! patched kernel:-) } if (no_reload>=0) { Scsi_Command cmd; if (cmd.associate (name,sb)) { cmd[0] = 0x1B; // START/STOP UNIT cmd[1] = 0x1; // "IMMED" cmd[4] = 0x3; // "Load" cmd[5] = 0; cmd.transport (); } errno=0; // ignore all errors on load } return 0; #endif } return 1; } extern "C" int get_mmc_profile (void *fd) { Scsi_Command cmd(fd); unsigned char buf[8],inq[128]; int profile=0,once=1,blank=0,err; unsigned int len; // INQUIRY is considered to be "non-intrusive" in a sense that // it won't interfere with any other operation nor clear sense // data, which might be important to retain for security reasons. cmd[0] = 0x12; // INQUIRY cmd[4] = 36; cmd[5] = 0; if ((err=cmd.transport(READ,inq,36))) sperror ("INQUIRY",err), exit (FATAL_START(errno)); // make sure we're talking to MMC unit, for security reasons... if ((inq[0]&0x1F) != 5) fprintf (stderr,":-( not an MMC unit!\n"), exit (FATAL_START(EINVAL)); do { cmd[0] = 0x46; cmd[8] = sizeof(buf); cmd[9] = 0; if ((err=cmd.transport(READ,buf,sizeof(buf)))) sperror ("GET CONFIGURATION",err), fprintf (stderr,":-( non-MMC unit?\n"), exit (FATAL_START(errno)); if ((profile = buf[6]<<8|buf[7]) || !once) break; // no media? cmd[0] = 0; // TEST UNIT READY cmd[5] = 0; if ((cmd.transport()&0xFFF00) != 0x23A00) break; // try to load tray... cmd[0] = 0x1B; // START/STOP UNIT cmd[4] = 0x3; // "Load" cmd[5] = 0; if ((err=cmd.transport ())) sperror ("LOAD TRAY",err), exit (FATAL_START(errno)); #if 1 wait_for_unit (cmd); #else // consume sense data, most likely "MEDIA MAY HAVE CHANGED" cmd[0] = 0; // TEST UNIT READY cmd[5] = 0; if ((err=cmd.transport ()) == -1) sperror ("TEST UNIT READY",err), exit (FATAL_START(errno)); #endif } while (once--); if (profile==0 || (profile&0x30)==0) // no or non-DVD media... return profile; cmd[0] = 0x51; // READ DISC INFORMATION cmd[8] = sizeof(disc_info); cmd[9] = 0; if ((err=cmd.transport (READ,disc_info,sizeof(disc_info)))) sperror ("READ DISC INFORMATION",err), exit (FATAL_START(errno)); // see if it's blank media if ((disc_info[2]&3) == 0) blank=0x10000; if (profile != 0x1A && profile != 0x13 && profile != 0x12) return blank|profile; cmd[0] = 0x23; // READ FORMAT CAPACITIES cmd[8] = 12; cmd[9] = 0; if ((err=cmd.transport (READ,formats,12))) sperror ("READ FORMAT CAPACITIES",err), exit (FATAL_START(errno)); len = formats[3]; if (len&7 || len<16) fprintf (stderr,":-( FORMAT allocaion length isn't sane"), exit (FATAL_START(EINVAL)); cmd[0] = 0x23; // READ FORMAT CAPACITIES cmd[7] = (4+len)>>8; cmd[8] = (4+len)&0xFF; cmd[9] = 0; if ((err=cmd.transport (READ,formats,4+len))) sperror ("READ FORMAT CAPACITIES",err), exit (FATAL_START(errno)); if (len != formats[3]) fprintf (stderr,":-( parameter length inconsistency\n"), exit(FATAL_START(EINVAL)); // see if it's not formatted if ((formats[8]&3) != 2) blank = 0x10000; return blank|profile; } static unsigned int get_2k_capacity (Scsi_Command &cmd) { unsigned char buf[32]; unsigned int ret=0; unsigned int nwa,free_blocks; int i,obligatory,len,err; obligatory=0x00; switch (mmc_profile&0xFFFF) { case 0x1A: // DVD+RW obligatory=0x26; case 0x13: // DVD-RW Restricted Overwrite for (i=8,len=formats[3];i>2) == obligatory) break; if (i==len) { fprintf (stderr,":-( can't locate obligatory format descriptor\n"); return 0; } ret = formats[4+i+0]<<24; ret |= formats[4+i+1]<<16; ret |= formats[4+i+2]<<8; ret |= formats[4+i+3]; nwa = formats[4+5]<<16|formats[4+6]<<8|formats[4+7]; if (nwa>2048) ret *= nwa/2048; else if (nwa<2048) ret /= 2048/nwa; break; case 0x12: // DVD-RAM // As for the moment of this writing I don't format DVD-RAM. // Therefore I just pull formatted capacity for now... ret = formats[4+0]<<24; ret |= formats[4+1]<<16; ret |= formats[4+2]<<8; ret |= formats[4+3]; nwa = formats[4+5]<<16|formats[4+6]<<8|formats[4+7]; if (nwa>2048) ret *= nwa/2048; else if (nwa<2048) ret /= 2048/nwa; break; case 0x11: // DVD-R case 0x14: // DVD-RW Sequential case 0x15: // DVD-R Dual Layer Sequential case 0x16: // DVD-R Dual Layer Jump case 0x1B: // DVD+R case 0x2B: // DVD+R Double Layer cmd[0] = 0x52; // READ TRACK INFORMATION cmd[1] = 1; cmd[4] = next_track>>8; cmd[5] = next_track&0xFF; // last track, set up earlier cmd[8] = sizeof(buf); cmd[9] = 0; if ((err=cmd.transport (READ,buf,sizeof(buf)))) { sperror ("READ TRACK INFORMATION",err); return 0; } nwa = 0; if (buf[7]&1) // NWA_V { nwa = buf[12]<<24; nwa |= buf[13]<<16; nwa |= buf[14]<<8; nwa |= buf[15]; } free_blocks = buf[16]<<24; free_blocks |= buf[17]<<16; free_blocks |= buf[18]<<8; free_blocks |= buf[19]; ret = nwa + free_blocks; break; default: break; } return ret; } extern "C" off64_t get_capacity (void *fd) { Scsi_Command cmd(fd); return (off64_t)get_2k_capacity(cmd)*2048; } extern "C" double get_buffer_stats (void *fd) { Scsi_Command cmd(fd); unsigned char bcap[12]; unsigned int bsize,bfree; cmd[0] = 0x5C; // READ BUFFER CAPACITY cmd[8] = sizeof(bcap); cmd[9] = 0; if (cmd.transport (READ,bcap,sizeof(bcap))) return 0.0; bsize = bcap[4]<<24|bcap[5]<<16|bcap[6]<<8|bcap[7]; bfree = bcap[8]<<24|bcap[9]<<16|bcap[10]<<8|bcap[11]; return (1.0 - (double)bfree/(double)bsize); } ssize_t poor_mans_pwrite64 (int fd,const void *_buff,size_t size,off64_t foff) { Scsi_Command cmd(ioctl_handle); /* screw first argument */ unsigned char bcap[12]; const unsigned char *buff=(const unsigned char *)_buff; unsigned int lba,nbl,bsize,bfree; int retries=0,errcode; static int dao_toggle=-1; if (foff&0x7FFF || size&0x7FFF) // 32K block size return (errno=EINVAL,-1); lba = foff>>11; nbl = size>>11; if (!media_written && next_wr_addr) { if ((lba+nbl) <= next_wr_addr) return size; else if (next_wr_addr > lba) nbl -= (next_wr_addr-lba), size -= (next_wr_addr-lba)<<11, buff += (next_wr_addr-lba)<<11, lba = next_wr_addr; } #if defined(__sun) || defined(sun) else next_wr_addr = lba; #endif if (dao_toggle<0) dao_toggle=is_dao; { static unsigned int first_wr_addr=0; if (!media_written) { sigs_mask (SIGS_BLOCK); first_wr_addr = lba; } else if (lba >= (first_wr_addr+buf_size/2)) // measured in 2KBs! { sigs_mask (SIGS_UNBLOCK); } } if (dao_blocks!=0 && (lba+nbl)>dao_blocks) nbl = dao_blocks-lba; while (1) { cmd[0] = wrvfy?0x2E:0x2A; // WRITE [AND VERIFY] (10) cmd[2] = (lba>>24)&0xff; // Logical Block Addrss cmd[3] = (lba>>16)&0xff; cmd[4] = (lba>>8)&0xff; cmd[5] = lba&0xff; cmd[7] = (nbl>>8)&0xff; cmd[8] = nbl&0xff; cmd[9] = 0; #if 0 cmd[0] = 0xAA; // WRITE(12) cmd[2] = (lba>>24)&0xff; // Logical Block Addrss cmd[3] = (lba>>16)&0xff; cmd[4] = (lba>>8)&0xff; cmd[5] = lba&0xff; cmd[8] = (nbl>>8)&0xff; cmd[9] = nbl&0xff; cmd[10] = 0x80; // "Streaming" cmd[11] = 0; #endif // // First writes can be long, especially in DAO mode... // I wish I could complement this with "if (lba==0)," // but some units might choose to fill the buffer before // they take the first nap... // cmd.timeout(dao_toggle?180:60); // // It should also be noted that under Linux these values // (if actually respected by kernel!) can turn out bogus. // The problem is that I scale them to milliseconds as // documentation requires/implies, while kernel treats // them as "jiffies." I could/should have used HZ macro // (or sysconf(_SC_CLK_TCK)), but recent kernels maintain // own higher HZ value and disrespects the user-land one. // Sending them down as milliseconds is just safer... // if (!(errcode=cmd.transport (WRITE,(void *)buff,size))) break; //--- WRITE failed ---// #if defined(__sun) || defined(sun) // // Solaris can slice USB WRITEs to multiple ones. Here I try // to find out which slice has failed. I expect we get here // only when we re-enter the loop... // if (lba==next_wr_addr && errcode==0x52102) // "INVALID ADDRESS FOR WRITE" { unsigned char track[32]; cmd[0] = 0x52; // READ TRACK INFORMATION cmd[1] = 1; cmd[4] = next_track>>8; cmd[5] = next_track&0xFF; cmd[8] = sizeof(track); cmd[9] = 0; if (!cmd.transport (READ,track,sizeof(track))) { if (track[7]&1) // NWA_V { next_wr_addr = track[12]<<24; next_wr_addr |= track[13]<<16; next_wr_addr |= track[14]<<8; next_wr_addr |= track[15]; if (lbanext_wr_addr) { nbl -= next_wr_addr-lba, size -= (next_wr_addr-lba)<<11, buff += (next_wr_addr-lba)<<11, lba = next_wr_addr; continue; } } } } #endif if (errcode==0x20408) // "LONG WRITE IN PROGRESS" { // Apparently only Pioneer units do this... if (velocity == 0) { if (handle_events(cmd) & (1<<6)) continue; goto sync_cache; } cmd[0] = 0x5C; // READ BUFFER CAPACITY cmd[8] = sizeof(bcap); cmd[9] = 0; if (cmd.transport (READ,bcap,sizeof(bcap))) bfree=0, bsize=buf_size; else { bsize = bcap[4]<<24|bcap[5]<<16|bcap[6]<<8|bcap[7]; bfree = bcap[8]<<24|bcap[9]<<16|bcap[10]<<8|bcap[11]; bsize /= 1024, bfree /= 1024; // xlate to KB } // check for sanity and drain 1/2 of the buffer if (bsize < buf_size/2) bsize = buf_size/2; else bsize /= 2; if (bfree > bsize) bfree = bsize; int msecs=0; if ((msecs=(bsize-bfree)*1000) > velocity) { msecs /= velocity; retries++; if (dao_toggle) dao_toggle=-1; } else // lots of free buffer reported? { if (dao_toggle) { dao_toggle=-1; if ((handle_events(cmd) & (1<<6))) continue; msecs = bsize*1000; msecs /= velocity; } else if (!retries++) continue; // Pioneer units seem to return bogus values at high // velocities now and then... I reserve for 4 ECC // blocks, which is ~6 ms at 16x. But note that some // OSes, Linux 2.4 among then, will nap for 10 or 20. msecs = (4*32*1000)/velocity; } // Retry values were increased because high-speed units // start recordings at only portion of velocity and it // takes more retries to write lead-in... Another common // reason for stucks are recalibrations at zone edges... if (retries > (dao_toggle?768:192)) fprintf (stderr,":-? the LUN appears to be stuck " "writing LBA=%xh, keep retrying in %dms\n", lba,msecs), retries=1; if (msecs>=0) { poll (NULL,0,msecs); continue; } lba |= 0x80000000; // signal insane bfree... } else if (lba==0 && errcode==0x20401) // "IN PROCESS OF BECOMING READY" { if ((handle_events(cmd) & (1<<6))) continue; if (++retries > 3) { fprintf (stderr,":-! the LUN is still in process of becoming ready, " "retrying in 5 secs...\n"), retries=0; sigs_mask (SIGS_UNBLOCK); poll (NULL,0,5000); } else sigs_mask (SIGS_UNBLOCK), poll (NULL,0,stdnap); continue; } else if (errcode==0x20404 || // "FORMAT IN PROGRESS" errcode==0x20407 || // "OPERATION IN PROGRESS" errcode==0x52C00) // "COMMAND SEQUENCE ERROR" { // Happens under automounter control? In general I recommend // to disable it! This code is a tribute to users who disregard // the instructions... sync_cache: cmd[0] = 0x35; // SYNC CACHE cmd[9] = 0; if (!cmd.transport()) { if (++retries > 1) { fprintf (stderr,":-! the LUN appears to be stuck at %xh, " "retrying in 5 secs...\n",lba), retries=0; sigs_mask (SIGS_UNBLOCK); poll (NULL,0,5000); } else if (errcode==0x52C00) fprintf (stderr,":-! \"COMMAND SEQUENCE ERROR\"@LBA=%xh. " "Is media being read?\n",lba); continue; } lba |= 0x40000000; // signal "can't SYNC CACHE" } { char cmdname[64]; sprintf (cmdname,"WRITE@LBA=%xh",lba), sperror (cmdname,errcode); } if (lba==0) { if (ASC(errcode)==0x30) fprintf (stderr,":-( media is not formatted or unsupported.\n"); else if ((mmc_profile&0xFFFF)==0x14 /*&& ASC(errcode)==0x64*/) fprintf (stderr,":-( attempt to re-run with " "-dvd-compat -dvd-compat to engage DAO or " "apply full blanking procedure\n"); } #if 1 // safety net: leave DL sessions open and resumable... if ((mmc_profile&0xFFFF)==0x2B && errcode!=0x52100) media_written=0; #endif return -1; } next_wr_addr = lba+nbl; media_written = 1; if (dao_toggle<0) dao_toggle=0; return size; } static void plus_rw_format (Scsi_Command &cmd) { int err,i,len; unsigned char descr[12]; if ((formats[4+4]&3) == 1) // Unformatted media { fprintf (stderr,"%s: pre-formatting blank DVD+RW...\n",ioctl_device); for (i=8,len=formats[3];i>2) == 0x26) break; if (i==len) fprintf (stderr,":-( can't locate DVD+RW format descriptor\n"), exit(FATAL_START(EMEDIUMTYPE)); memset (descr,0,sizeof(descr)); descr[1]=0x02; // "IMMED" flag descr[3]=0x08; // "Descriptor Length" (LSB) memcpy (descr+4,formats+4+i,4); descr[8]=0x26<<2; // "Format type" 0x26 cmd[0] = 0x04; // FORMAT UNIT cmd[1] = 0x11; // "FmtData" and "Format Code" cmd[5] = 0; if ((err=cmd.transport(WRITE,descr,sizeof(descr)))) sperror ("FORMAT UNIT",err), exit(FATAL_START(errno)); wait_for_unit (cmd); cmd[0] = 0x35; // FLUSH CACHE cmd[9] = 0; if ((err=cmd.transport())) sperror ("FLUSH CACHE",err), exit(FATAL_START(errno)); } } static int plus_rw_restart_format (Scsi_Command &cmd, off64_t size) { unsigned char descr[12]; unsigned int lead_out,blocks; int err,i,len; if (!dvd_compat && size!=0) { blocks = size/2048; blocks += 15, blocks &= ~15; lead_out = 0; lead_out |= formats[4+0], lead_out <<= 8; lead_out |= formats[4+1], lead_out <<= 8; lead_out |= formats[4+2], lead_out <<= 8; lead_out |= formats[4+3]; if (blocks<=lead_out) // no need to resume format... return 0; } fprintf (stderr,"%s: restarting DVD+RW format...\n",ioctl_device); for (i=8,len=formats[3];i>2) == 0x26) break; if (i==len) fprintf (stderr,":-( can't locate DVD+RW format descriptor\n"), exit(FATAL_START(EMEDIUMTYPE)); memset (descr,0,sizeof(descr)); descr[1]=0x02; // "IMMED" flag descr[3]=0x08; // "Descriptor Length" (LSB) #if 1 memcpy (descr+4,formats+4+i,4); #else memset (descr+4,-1,4); #endif descr[8]=0x26<<2; // "Format type" 0x26 descr[11]=1; // "Restart Format" cmd[0] = 0x04; // FORMAT UNIT cmd[1] = 0x11; // "FmtData" and "Format Code" cmd[5] = 0; if ((err=cmd.transport(WRITE,descr,sizeof(descr)))) { sperror ("RESTART FORMAT",err); return 1; } wait_for_unit(cmd); return 0; } extern "C" int plusminus_r_C_parm (void *fd,char *C_parm) { unsigned int next_session, prev_session,err; Scsi_Command cmd(fd); unsigned char buf[36]; if ((disc_info[2]&3) != 1) fprintf (stderr,":-( media is not appendable\n"), exit(FATAL_START(EMEDIUMTYPE)); // allow to resume even --v-- incomplete sessions // if (((disc_info[2]>>2)&3) > 1) fprintf (stderr,":-( last session is not empty\n"), exit(FATAL_START(EMEDIUMTYPE)); next_track = disc_info[5]|disc_info[10]<<8; cmd[0] = 0x52; // READ TRACK INFORMATION cmd[1] = 1; cmd[4] = next_track>>8; cmd[5] = next_track; // ask for last track cmd[8] = sizeof(buf); cmd[9] = 0; if ((err=cmd.transport (READ,buf,sizeof(buf)))) { sperror ("READ TRACK INFORMATION",err); if (ASC(err)==0x24) // hp dvd200i returns 24 if media is full fprintf (stderr,":-( media must be full already\n"); exit (FATAL_START(errno)); } #if 0 if (buf[7]&1) // NWA_V { next_session = buf[12]<<24; next_session |= buf[13]<<16; next_session |= buf[14]<<8; next_session |= buf[15]; } else fprintf (stderr,":-( Next Writable Address is invalid\n"), exit(FATAL_START(EMEDIUMTYPE)); #else next_session = buf[8]<<24; // Track Start Address next_session |= buf[9]<<16; next_session |= buf[10]<<8; next_session |= buf[11]; #endif // // All manuals say the data is fabricated, presumably implying // that one should use another command. But we stick to this one // because kernel uses this very command to mount multi-session // discs. // cmd[0] = 0x43; // READ TOC cmd[2] = 1; // "Session info" cmd[8] = 12; cmd[9] = 0; if ((err=cmd.transport (READ,buf,12))) sperror ("READ SESSION INFO",err), exit (FATAL_START(errno)); prev_session = buf[8]<<24; prev_session |= buf[9]<<16; prev_session |= buf[10]<<8; prev_session |= buf[11]; sprintf (C_parm,"%d,%d",prev_session+16,next_session); return next_session; } static unsigned char *pull_page2A (Scsi_Command &cmd) { unsigned char *page2A,header[12]; unsigned int len,bdlen; int err; cmd[0] = 0x5A; // MODE SENSE cmd[1] = 0x08; // "Disable Block Descriptors" cmd[2] = 0x2A; // "Capabilities and Mechanical Status" cmd[8] = sizeof(header); // header only to start with cmd[9] = 0; if ((err=cmd.transport(READ,header,sizeof(header)))) sperror ("MODE SENSE#2A",err), exit(FATAL_START(errno)); len = (header[0]<<8|header[1])+2; bdlen = header[6]<<8|header[7]; if (bdlen) // should never happen as we set "DBD" above { if (len < (8+bdlen+30)) fprintf (stderr,":-( LUN is impossible to bear with...\n"), exit(FATAL_START(EINVAL)); } else if (len < (8+2+(unsigned int)header[9]))// SANYO does this. len = 8+2+header[9]; page2A = (unsigned char *)malloc(len); if (page2A == NULL) fprintf (stderr,":-( memory exhausted\n"), exit(FATAL_START(ENOMEM)); cmd[0] = 0x5A; // MODE SENSE cmd[1] = 0x08; // "Disable Block Descriptors" cmd[2] = 0x2A; // "Capabilities and Mechanical Status" cmd[7] = len>>8; cmd[8] = len; // real length this time cmd[9] = 0; if ((err=cmd.transport(READ,page2A,len))) sperror ("MODE SENSE#2A",err), exit(FATAL_START(errno)); len -= 2; if (len < ((unsigned int)page2A[0]<<8|page2A[1])) // paranoia:-) page2A[0] = len>>8, page2A[1] = len; return page2A; } static int pull_velocity (Scsi_Command &cmd,unsigned char *d) { unsigned int len; int v,err; class autofree perf; #if 0 // 8x AccessTek derivatives, such as OptoRite DD0405 if ((d[4]&2) == 0) return -1; #endif len = (d[0]<<24|d[1]<<16|d[2]<<8|d[3])-4; if (len%16) return -1; if (len==0) return -1; // LG GCA-4040N:-( len += 8; if (len == (8+16)) { velocity = d[8+ 4]<<24|d[8+ 5]<<16|d[8+ 6]<<8|d[8+ 7]; v = d[8+12]<<24|d[8+13]<<16|d[8+14]<<8|d[8+15]; if (v>velocity) velocity=v; // CAV? } else // ZCLV { unsigned int n = (len-8)/16; unsigned char *p; perf = (unsigned char *)malloc (len); if (perf == NULL) fprintf (stderr,":-( memory exhausted\n"), exit(FATAL_START(ENOMEM)); cmd[0]=0xAC; // GET PERFORMANCE cmd[1]=4; // ask for "Overall Write Performance" cmd[8]=n>>8; cmd[9]=n; // real number of descriptors cmd[10]=0; // ask for descriptor in effect cmd[11]=0; if ((err=cmd.transport(READ,perf,len))) sperror ("GET CURRENT PERFORMANCE",err), exit (FATAL_START(errno)); // Pick the highest speed... for (p=perf+8,len-=8;len;p+=16,len-=16) { v=p[ 4]<<24|p[ 5]<<16|p[ 6]<<8|p[ 7]; if (v > velocity) velocity = v; v=p[12]<<24|p[13]<<16|p[14]<<8|p[15]; if (v > velocity) velocity = v; // ZCAV? } } return 0; } static int set_speed_B6h (Scsi_Command &cmd,unsigned int dvddash, unsigned char *page2A) { unsigned int len; int err; unsigned char d[8+16]; class autofree perf; cmd[0]=0xAC; // GET PERFORMACE cmd[9]=1; // start with one descriptor cmd[10]=0x3; // ask for "Write Speed Descriptor" cmd[11]=0; if ((err=cmd.transport(READ,d,sizeof(d)))) { sperror ("GET PERFORMANCE",err); fprintf (stderr,":-( falling down to SET CD SPEED\n"); return -1; } len = (d[0]<<24|d[1]<<16|d[2]<<8|d[3])-4; if (len%16) // insane length { fprintf (stderr,":-( GET PERFORMANCE: insane Performance Data Length\n"); return -1; } perf = (unsigned char *)malloc(len+=8); if (perf == NULL) fprintf (stderr,":-( memory exhausted\n"), exit(FATAL_START(ENOMEM)); if (len == sizeof(d)) memcpy (perf,d,sizeof(d)); else { unsigned int n=(len-8)/16; cmd[0]=0xAC; // GET PERFORMANCE cmd[8]=n>>8; cmd[9]=n; // real number of descriptors cmd[10]=0x3; // ask for "Write Speed Descriptor" cmd[11]=0; if ((err=cmd.transport(READ,perf,len))) sperror ("GET PERFORMANCE",err), exit (FATAL_START(errno)); } int targetv=0,errp=0; do { memset (d,0,sizeof(d)); cmd[0]=0xAC; // GET PERFORMANCE cmd[1]=4; // ask for "Overall Write performance" cmd[9]=1; cmd[10]=0; // ask for descriptor in effect cmd[11]=0; if (errp || (errp=cmd.transport(READ,d,sizeof(d)))) #if 0 sperror ("GET CURRENT PERFORMANCE",errp), exit (FATAL_START(errno)); // well, if it passed above, we // expect it to pass this too... #else // Pioneer doesn't report current speed through GET PERFORMANCE:-( { emulated_err: if (page2A == NULL) return -1; unsigned int plen,hlen; plen = (page2A[0]<<8|page2A[1]) + 2; hlen = 8 + (page2A[6]<<8|page2A[7]); unsigned char * const p = page2A + hlen; if (plen<(hlen+32) || p[1]<(32-2)) return -1; // well, SET CD SPEED wouldn't work... velocity = p[28]<<8|p[29]; } #endif else if ((errp = pull_velocity (cmd,d)) < 0) goto emulated_err; if (speed_factor != 0.0) { int i,j=len-8,v,v0,v1,minv,closesti,closestv=0; unsigned char *wsdp=perf+8; for (minv=0x7fffffff,i=0;i(15*ONEX)) speed_factor /= 16.0; else if (minv>(11*ONEX)) speed_factor /= 12.0; else if (minv>(7*ONEX)) speed_factor /= 8.0; else if (minv>(5*ONEX)) speed_factor /= 6.0; else if (minv>(3*ONEX)) speed_factor /= 4.0; else if (!dvddash && minv>(2*ONEX)) // 2.4x is like 1x for DVD+, but it turned out that there're // units, most notably "DVDRW IDE1004," burning DVD+ at // lower speed, so I have to watch out for it... speed_factor /= 2.4; else if (minv>=(2*ONEX)) speed_factor /= 2.0; v0=(int)(speed_factor*minv + 0.5); for (closesti=0,minv=0x7fffffff,i=0;i>24; pd[9]=cap>>16; pd[10]=cap>>8; pd[11]=cap; #endif memcpy(pd+12,wsdp+closesti+8,4); // copy "Read Speed" memcpy(pd+20,wsdp+closesti+12,4); // copy "Write Speed" pd[18]=pd[26]=1000>>8; // set both "Read Time" and pd[19]=pd[27]=1000&0xFF; // "Write Time" to 1000ms cmd[0]=0xB6; // SET STREAMING cmd[10]=sizeof(pd); cmd[11]=0; if ((err=cmd.transport (WRITE,pd,sizeof(pd)))) sperror ("SET STREAMING",err), exit (FATAL_START(errno)); // I pull the Page 2A in either case, because unit might // have provided irrelevant information (Plextor). Not to // mention cases when it simply failed to reply to GET // CURRENT PERFORMANCE (Pioneer, LG). unsigned int plen = (page2A[0]<<8|page2A[1]) + 2; cmd[0] = 0x5A; // MODE SENSE cmd[1] = 0x08; // "Disable Block Descriptors" cmd[2] = 0x2A; // "Capabilities and Mechanical Status" cmd[7] = plen>>8; cmd[8] = plen; // real length this time cmd[9] = 0; if ((err=cmd.transport(READ,page2A,plen))) sperror ("MODE SENSE#2A",err), exit(FATAL_START(errno)); } else if (targetv) { if (targetv!=velocity) { if (errp==0) // check Page 2A for speed then... { errp=-1; continue; } if (velocity==0 || velocity>=ONEX) { fprintf(stderr,":-? Failed to change write speed: %d->%d\n", (int)velocity,targetv); if (velocity%d\n", (int)velocity,targetv); exit (FATAL_START(EINVAL)); } } break; } } while (targetv); return targetv; } static int set_speed_BBh (Scsi_Command &cmd,unsigned char *page2A) { unsigned int plen,hlen; int err; plen = (page2A[0]<<8|page2A[1]) + 2; hlen = 8 + (page2A[6]<<8|page2A[7]); unsigned char * const p = page2A + hlen; if (plen<(hlen+32) || p[1]<(32-2)) return -1; int targetv=0; do { velocity = p[28]<<8|p[29]; if (speed_factor != 0.0) { int i,j,v,v0,v1,minv,closesti,closestv=0; unsigned char *wsdp=p+32; j=(p[30]<<8|p[31])*4; for (minv=0x7fffffff,i=0;i>8; cmd[8] = plen; // real length this time cmd[9] = 0; if ((err=cmd.transport(READ,page2A,plen))) sperror ("MODE SENSE#2A",err), exit(FATAL_START(errno)); } else if (targetv) { if (targetv!=velocity) fprintf (stderr,":-( Failed to change write speed: %d->%d\n", (int)velocity,targetv), exit (FATAL_START(EINVAL)); break; } } while (targetv); return targetv; } static int plusminus_pages_setup (Scsi_Command &cmd,int profile) { unsigned int plen,hlen,dvddash; unsigned char header[16],p32; int err; class autofree page2A; switch (profile) { case 0x1A: // DVD+RW p32 = 0; dvddash = 0; break; case 0x1B: // DVD+R // // Even though MMC-4 draft explicitely exempts DVD+RW/+R media // from those being affected by Page 05h settings, some // firmwares apparently pay attention to Multi-session flag // when finalizing DVD+R media. Well, we probably can't blame // vendors as specification is still a work in progress, not to // mention that it was published after DVD+R was introduced to // the market. // p32 = dvd_compat?0x02:0xC0; dvddash = 0; break; case 0x13: // DVD-RW Restricted Overwrite p32 = 0xC0; // NB! Test Write in not option here dvddash = 1; break; case 0x11: // DVD-R Sequential case 0x14: // DVD-RW Sequential case 0x15: // DVD-R Dual Layer Sequential p32 = 0xC0; if (next_track==1) do { if (dvd_compat >= (profile==0x14?2:256)) { is_dao = 1, fprintf (stderr,"%s: engaging DVD-%s DAO upon user request...\n", ioctl_device,profile==0x14?"RW":"R"); break; } // Try to figure out if we have to go for DAO... cmd[0] = 0x46; // GET CONFIGURATION cmd[1] = 2; // ask for the only feature... cmd[3] = 0x21; // the "Incremental Streaming Writable" one cmd[8] = 16; // The feature should be there, right? cmd[9] = 0; if ((err=cmd.transport (READ,header,16))) sperror ("GET FEATURE 21h",err), exit(FATAL_START(errno)); hlen = header[0]<<24|header[1]<<16|header[2]<<8|header[3]; // See if Feature 21h is "current," if not, engage DAO... if (hlen>=12 && (header[8+2]&1)==0) { is_dao = dvd_compat = 1; fprintf (stderr,"%s: FEATURE 21h is not on, engaging DAO...\n", ioctl_device); break; } } while (0); if (is_dao) p32 |=2; // DAO if (profile==0x15 || // DVD-R DL Seq has no notion of multi-session dvd_compat) p32 &= 0x3F; // Single-session if (test_write) p32 |= 0x10; // Test Write for debugging purposes dvddash=1; break; case 0x16: // DVD-R Dual Layer Jump p32 = 0xC0; p32 |= 4; // Jump if (test_write) p32 |= 0x10; // Test Write for debugging purposes dvddash=1; break; default: p32 = 0; dvddash = 0; break; } if (dvddash || p32) page05_setup (cmd,profile,p32); page2A = pull_page2A (cmd); plen = (page2A[0]<<8|page2A[1]) + 2; hlen = 8 + (page2A[6]<<8|page2A[7]); unsigned char * const p = page2A + hlen; if (plen<(hlen+14) || p[1]<(14-2)) // no "Buffer Size Supported" buf_size = 512; // bogus value else buf_size = (p[12]<<8|p[13]); if (buf_size<512) // bogus value buf_size = 512; // GET PERFORMANCE/SET STREAMING are listed as mandatory, so that // this is actually the most likely path... if (set_speed_B6h (cmd,dvddash,page2A) >= 0) goto opc; if (plen<(hlen+30) || p[1]<(30-2)) // no "Current Write Speed" present { if (dvddash) { fprintf (stderr,":-( \"Current Write Speed\" descriptor " "is not present, faking 1x...\n"), velocity = ONEX; speed_factor = 0.0; } else velocity = 0; } else do { velocity = p[28]<<8|p[29]; // Some units, most notably NEC and derivatives, were observed // to report CD-R descriptors... if (velocity < ONEX) // must be bogus { velocity=0; break; } if (plen<(hlen+32) || p[1]<(32-2)) // no write descriptors break; int n = p[30]<<8|p[31],minv=1<<16; unsigned char *p_= p+32; for (;n--;p_+=4) { int v=p_[2]<<8|p_[3]; if (v==0) continue; // Lite-On returns zeros here... if (v2*ONEX) // See corresponding comment in set_speed_B6h() speed_factor /= 2.4; } while (0); if (speed_factor != 0.0 && (velocity==0 || set_speed_BBh (cmd,page2A)<0) ) fprintf (stderr,":-( can't control writing velocity, " "-speed option is ignored\n"); opc: if (!dvddash || test_write || no_opc) return 0; // See if OPC is required... cmd[0] = 0x51; // READ DISC INFORMATION cmd[8] = 8; cmd[9] = 0; if ((err=cmd.transport (READ,header,8))) sperror ("READ DISC INFORMATION",err), exit(FATAL_START(errno)); if ((header[0]<<8|header[1]) <= 0x20) { cmd[0] = 0x54; // SEND OPC INFORMATION cmd[1] = 1; // "Perform OPC" cmd[9] = 0; cmd.timeout(120); // NEC units can be slooo...w if ((err=cmd.transport())) do { if (err==0x17301) // "POWER CALIBRATION AREA ALMOST FULL" { fprintf (stderr,":-! WARNING: Power Calibration Area " "is almost full\n"); break; } sperror ("PERFORM OPC",err); if ((err&0x0FF00)==0x07300) exit(FATAL_START(errno)); /* * Th rest of errors are ignored, most notably observed: * 0x52000, "INVALID COMMAND" Panasonic LD-F321 * 0x52700, "WRITE PROTECTED" Plextor PX-712A * 0x52C00, "COMMAND SEQUENCE ERROR" Plextor PX-716UF * 0x53002, "MEDIA NOT SUPPORTED" Toshiba TS-H542A */ } while (0); } handle_events(cmd); return 0; } static int minus_rw_quickgrow (Scsi_Command &cmd,off64_t size) { unsigned char format[12]; unsigned int lead_out,blocks,type; int i,len,err; type = formats[4+4]&3; if (type==2 && size!=0) { blocks = size/2048; blocks += 15, blocks &= ~15; lead_out = 0; lead_out |= formats[4+0], lead_out <<= 8; lead_out |= formats[4+1], lead_out <<= 8; lead_out |= formats[4+2], lead_out <<= 8; lead_out |= formats[4+3]; if (blocks<=lead_out) // no need to grow the session... return 0; } // look for Quick Grow descriptor... for (i=8,len=formats[3];i>2) == 0x13) break; if (i==len) // no Quick Grow descriptor { if (type != 2) quickgrown=1; // in reality quick formatted... return 0; } else { blocks = 0; blocks |= formats[i+0], blocks <<= 8; blocks |= formats[i+1], blocks <<= 8; blocks |= formats[i+2], blocks <<= 8; blocks |= formats[i+3]; if (type==2 && blocks==0) // nowhere no grow... return 0; } quickgrown=1; fprintf (stderr,"%s: \"Quick Grow\" session...\n",ioctl_device); memset (format,0,sizeof(format)); format [1] = 2; // "IMMED" format [3] = 8; // "Length" format [8] = 0x13<<2; // "Quick Grow" format [11] = 16; cmd[0] = 0x4; // FORMAT UNIT cmd[1] = 0x11; cmd[5] = 0; if ((err=cmd.transport (WRITE,format,sizeof(format)))) { sperror ("QUICK GROW",err); return 1; } return wait_for_unit (cmd); } static int minus_r_reserve_track (Scsi_Command &cmd,off64_t size) { int err; unsigned int blocks; blocks = size/2048; if (is_dao) dao_blocks = blocks; else blocks += 15, blocks &= ~15; fprintf (stderr,"%s: reserving %u blocks",ioctl_device,blocks); if (is_dao && blocks<380000) fprintf (stderr,"\b, warning for short DAO recording"), poll (NULL,0,5000); fprintf (stderr,"\n"); cmd[0] = 0x53; // RESERVE TRACK cmd[5] = blocks>>24; cmd[6] = blocks>>16; cmd[7] = blocks>>8; cmd[8] = blocks; cmd[9] = 0; if ((err=cmd.transport ())) { sperror ("RESERVE TRACK",err); return 1; } return 0; } static void plus_r_dl_split (Scsi_Command &cmd,off64_t size) { int err; unsigned int blocks,split; unsigned char dvd_20[4+8]; cmd[0] = 0xAD; // READ DVD STRUCTURE cmd[7] = 0x20; // "DVD+R Double Layer Boundary Information" cmd[9] = sizeof(dvd_20); cmd[11] = 0; if ((err=cmd.transport(READ,dvd_20,sizeof(dvd_20)))) sperror ("READ DVD+R DL BOUNDARY INFORMATION",err), exit (FATAL_START(errno)); if ((dvd_20[0]<<8|dvd_20[1]) < 10) fprintf (stderr,":-( insane DVD+R DL BI structure length\n"), exit (FATAL_START(EINVAL)); if (dvd_20[4]&0x80) { fprintf (stderr,":-? L0 Data Zone Capacity is set already\n"); return; } split = dvd_20[8]<<24|dvd_20[9]<<16|dvd_20[10]<<8|dvd_20[11]; blocks = size/2048; blocks += 15, blocks &= ~15; if (blocks <= split) fprintf (stderr,":-( more than 50%% of space will be *wasted*!\n" " use single layer media for this recording\n"), exit (FATAL_START(EMEDIUMTYPE)); blocks /= 16; blocks += 1; blocks /= 2; blocks *= 16; fprintf (stderr,"%s: splitting layers at %u blocks\n", ioctl_device,blocks); memset (dvd_20,0,sizeof(dvd_20)); dvd_20[1] = sizeof(dvd_20)-2; dvd_20[8] = blocks>>24; dvd_20[9] = blocks>>16; dvd_20[10] = blocks>>8; dvd_20[11] = blocks; cmd[0] = 0xBF; // SEND DVD STRUCTURE cmd[7] = 0x20; // "DVD+R Double Layer Recording Information" cmd[9] = sizeof(dvd_20); cmd[11] = 0; if ((err=cmd.transport(WRITE,dvd_20,sizeof(dvd_20)))) sperror ("SEND DVD+R DOUBLE LAYER RECORDING INFORMATION",err), exit (FATAL_START(errno)); } static int flush_cache (Scsi_Command &cmd, int even_sync=1) { int err; cmd[0] = 0x35; // FLUSH CACHE cmd[1] = 0x02; // "IMMED" cmd[9] = 0; if (!(err=cmd.transport())) wait_for_unit (cmd); else sperror ("FLUSH CACHE",err); if (even_sync) // Pioneer apparently needs this, { // non-IMMED FLUSH that is... cmd[0] = 0x35; // FLUSH CACHE cmd[9] = 0; if (is_dao) cmd.timeout (15*60); if ((err=cmd.transport())) { sperror ("SYNCHRONOUS FLUSH CACHE",err); return err; } } return 0; } // // atexit/signal handlers // extern "C" void no_r_finalize () { while (media_written) { Scsi_Command cmd(ioctl_handle); fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache (cmd)) break; pioneer_stop (cmd); media_written = 0; errno = 0; } _exit (errno); } extern "C" void plus_rw_finalize () { sigs_mask(SIGS_BLOCK); while (media_written) { Scsi_Command cmd(ioctl_handle); int err; fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache (cmd)) break; if (!dvd_compat) { fprintf (stderr,"%s: stopping de-icing\n",ioctl_device); cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0; // "Stop De-Icing" cmd[9] = 0; if ((err=cmd.transport())) sperror ("STOP DE-ICING",err); if (wait_for_unit (cmd)) break; } fprintf (stderr,"%s: writing lead-out\n",ioctl_device); cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0x02; // "Close session" cmd[9] = 0; if ((err=cmd.transport())) sperror ("CLOSE SESSION",err); if (wait_for_unit (cmd)) break; media_written = 0; next_wr_addr = 0; errno = 0; if (do_reload && media_reload()) perror (":-( unable to reload tray");// not actually reached } _exit (errno); } extern "C" void plus_r_finalize () { sigs_mask(SIGS_BLOCK); while (media_written) { Scsi_Command cmd(ioctl_handle); int mode,err; fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache(cmd)) break; fprintf (stderr,"%s: closing track\n",ioctl_device); cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0x01; // "Close Track" cmd[4] = next_track>>8; cmd[5] = next_track; cmd[9] = 0; if ((err=cmd.transport())) sperror ("CLOSE TRACK",err); if (wait_for_unit (cmd)) break; if (dvd_compat) // HP dvd520n insists on "finalize with minimum radius" // with DVD+R DL ---------------vv mode = ((mmc_profile&0xFFFF)==0x2B)?0x05:0x06, fprintf (stderr,"%s: closing disc\n",ioctl_device); else mode = 0x02, fprintf (stderr,"%s: closing session\n",ioctl_device); while (1) // Pioneer DVR-K04RA { cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = mode; // "Close session" cmd[9] = 0; err = cmd.transport(); if (err == 0x20407) // "OP IN PROGRESS" { poll (NULL,0,stdnap); continue; } else if (err) sperror (dvd_compat?"CLOSE DISC":"CLOSE SESSION",err); break; } if (wait_for_unit (cmd)) break; media_written = 0; errno = 0; if (do_reload && media_reload()) perror (":-( unable to reload tray");// not actually reached } _exit (errno); } extern "C" void minus_rw_finalize () { sigs_mask(SIGS_BLOCK); while (media_written) { Scsi_Command cmd(ioctl_handle); int err; fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache(cmd)) break; if (quickgrown) { fprintf (stderr,"%s: writing lead-out\n",ioctl_device); cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0x02; // "Close Session" cmd[9] = 0; if ((err=cmd.transport())) sperror ("CLOSE SESSION",err); if (wait_for_unit (cmd)) break; quickgrown=0; } media_written = 0; next_wr_addr = 0; errno = 0; if (do_reload) { if (media_reload()) perror (":-( unable to reload tray");// not actually reached } else return; // Restricted Overwrite is just ugly! } _exit (errno); } extern "C" void minus_r_finalize () { sigs_mask(SIGS_BLOCK); while (media_written) { Scsi_Command cmd(ioctl_handle); int err; fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache(cmd,!is_dao)) break; if (!is_dao) { fprintf (stderr,"%s: updating RMA\n",ioctl_device); cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0x01; // "Close Track" cmd[4] = next_track>>8; cmd[5] = next_track; cmd[9] = 0; if ((err=cmd.transport())) sperror ("CLOSE TRACK",err); if (wait_for_unit (cmd)) break; fprintf (stderr,"%s: closing %s\n",ioctl_device, dvd_compat?"disc":"session"); while (1) // Pioneer DVR-K04RA { cmd[0] = 0x5B; // CLOSE TRACK/SESSION cmd[1] = 0x01; // "IMMED" cmd[2] = 0x02; // "Close Session" cmd[9] = 0; err = cmd.transport(); if (err == 0x20407) // "OP IN PROGRESS" { poll (NULL,0,stdnap); continue; } else if (err) sperror (dvd_compat?"CLOSE DISC":"CLOSE SESSION",err); break; } if (wait_for_unit (cmd)) break; } media_written = 0; errno = 0; if (do_reload && media_reload()) perror (":-( unable to reload tray");// not actually reached } _exit (errno); } extern "C" void ram_reload () { sigs_mask(SIGS_BLOCK); if (media_written && do_reload) media_reload(); _exit (errno); } // // poor_mans_setup takes care of a lot of things. // It's invoked right before first write and if necessary/applicable // prepares Page 05, reserves track, sets up atexit and signal handlers. // typedef ssize_t (*pwrite64_t)(int,const void *,size_t,off64_t); extern "C" pwrite64_t poor_mans_setup (void *fd,off64_t leadout) { Scsi_Command cmd(ioctl_handle=fd); int err,profile=mmc_profile&0xFFFF; // We might have loaded media ourselves, in which case we // should lock the door... cmd[0] = 0x1E; // PREVENT/ALLOW MEDIA REMOVAL cmd[4] = 1; // "Prevent" cmd[5] = 0; if ((err=cmd.transport ())) sperror ("PREVENT MEDIA REMOVAL",err), exit (FATAL_START(errno)); handle_events(cmd); switch (profile) { case 0x1A: // DVD+RW switch (disc_info[7]&3) // Background formatting { case 0: // blank plus_rw_format (cmd); break; case 1: // suspended plus_rw_restart_format (cmd,leadout); break; case 2: // in progress case 3: // complete break; } plusminus_pages_setup (cmd,profile); atexit (plus_rw_finalize); atsignals (plus_rw_finalize); break; case 0x1B: // DVD+R case 0x2B: // DVD+R Double Layer plusminus_pages_setup(cmd,profile); if (profile==0x2B && next_track==1 && dvd_compat && leadout) plus_r_dl_split (cmd,leadout); atexit (plus_r_finalize); if (next_wr_addr) { atsignals (no_r_finalize); next_wr_addr=(unsigned int)-1; } else atsignals (plus_r_finalize); break; case 0x13: // DVD-RW Restricted Overwrite plusminus_pages_setup (cmd,profile); // // A side note. Quick Grow can't be performed earlier, // as then reading is not possible. // minus_rw_quickgrow (cmd,leadout); atexit (minus_rw_finalize); atsignals (minus_rw_finalize); break; case 0x11: // DVD-R Sequential case 0x14: // DVD-RW Sequential case 0x15: // DVD-R Dual Layer Sequential plusminus_pages_setup (cmd,profile); if ((profile==0x15) && leadout) { unsigned char dvd_10[4+16]; unsigned int layer_size,data_size=leadout/2048; cmd[0] = 0xAD; // READ DVD STRUCTURE cmd[7] = 0x10; cmd[9] = sizeof(dvd_10); cmd[11] = 0; if ((err=cmd.transport(READ,dvd_10,sizeof(dvd_10)))) sperror("READ DVD STRUCTURE #10h",err), exit(FATAL_START(errno)); layer_size = dvd_10[4+13]<<16, layer_size |= dvd_10[4+14]<<8, layer_size |= dvd_10[4+15]; if (data_size <= layer_size) fprintf (stderr,":-( more than 50%% of space will be *wasted*!\n" " use single layer media for this recording\n"), exit(FATAL_START(EMEDIUMTYPE)); } if (is_dao && leadout) minus_r_reserve_track(cmd,leadout); atexit (minus_r_finalize); if (next_wr_addr) { atsignals (no_r_finalize); next_wr_addr=(unsigned int)-1; } else atsignals (minus_r_finalize); break; case 0x12: // DVD-RAM // exclusively for speed settings... plusminus_pages_setup (cmd,profile); atexit (ram_reload); atsignals (ram_reload); break; case 0x16: // DVD-R Dual Layer Jump not supported for reasons // discussed at web-page... default: fprintf (stderr,":-( mounted media[%X] is not supported\n",profile), exit(FATAL_START(EMEDIUMTYPE)); break; } if (velocity) fprintf (stderr,"%s: \"Current Write Speed\" is %.1fx1385KBps.\n", ioctl_device,velocity/(double)ONEX); if (next_wr_addr==(unsigned int)-1) do { unsigned char track[32]; next_wr_addr = 0; cmd[0] = 0x52; // READ TRACK INFORMATION cmd[1] = 1; cmd[4] = next_track>>8; cmd[5] = next_track&0xFF; // last track, set up earlier cmd[8] = sizeof(track); cmd[9] = 0; if (cmd.transport (READ,track,sizeof(track))) break; if (track[7]&1) // NWA_V { next_wr_addr = track[12]<<24; next_wr_addr |= track[13]<<16; next_wr_addr |= track[14]<<8; next_wr_addr |= track[15]; } if (next_wr_addr != // track start (unsigned int)(track[8]<<24|track[9]<<16|track[10]<<8|track[11])) fprintf (stderr,":-? resuming track#%d from LBA#%d\n", next_track,next_wr_addr); } while (0); else next_wr_addr = 0; return poor_mans_pwrite64; } extern "C" int poor_man_rewritable (void *fd, void *buf) { Scsi_Command cmd(fd); int err,profile=mmc_profile&0xFFFF; if (profile!=0x13 && // not DVD-RW Restricted Overwrite profile!=0x12 && // nor DVD-RAM profile!=0x1A && // nor DVD+RW profile!=0x2A) // nor DVD+RW Double Layer return 0; if (profile==0x13) // DVD-RW Restricted Overwrite { // Yet another restriction of Restricted Overwrite mode? // Pioneer DVR-x05 can't read a bit otherwise... do_reload=0; minus_rw_finalize (); // with do_reload==0! do_reload=1; } else if (profile==0x1A || // DVD+RW profile==0x2A) // DVD+RW Double Layer { fprintf (stderr,"%s: flushing cache\n",ioctl_device); if (flush_cache(cmd)) exit (errno); } cmd[0] = 0x28; // READ(10) cmd[5] = 16; // LBA#16, volume descriptor set cmd[8] = 16; // 32KB cmd[9] = 0; if ((err=cmd.transport (READ,buf,16*2048))) sperror ("READ@LBA=10h",err), exit(errno); return 1; }