#!/usr/local/bin/ruby -w # # vim:sw=2 ts=8:et sta # # # Copyright (c) 2003, 2004, 2005 Ariff Abdullah # (skywizard@MyBSD.org.my) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $MyBSD$ # # Date: Sat Dec 20 23:07:10 MYT 2003 # OS: FreeBSD kasumi.MyBSD.org.my 4.7-RELEASE i386 # # cookcd.rb(8), IDE/ATAPI cd burner entirely written using ruby. # Compatible with burncd (FreeBSD) # http://www.freebsd.org/cgi/cvsweb.cgi/src/usr.sbin/burncd # # Most of the logic taken from burncd(8) itself, with few differences: # (including why this has been written in the first place) # * fix burncd broken DAO burning (audio, data/raw). Other type of # burning mode not tested (yet). /usr/sbin/burncd generate flaw TOC/CUE # resulting broken DAO cd. # * automatically truncate wave header if data mode is audio # * 10 seconds confirmation timeout before proceed simmilar with cdrecord # (use '-y' to supress it) # # Caveat: since this is written entirely using ruby, probably it's # compatibility is nowhere but FreeBSD 4.x/5.x /usr/include/sys/cdio.h # and /usr/include/sys/cdrio.h # TODO: Proper machine byte ordering / endianess check # require 'getoptlong' def MIN(x, y) x > y ? y : x end module IOCCOM PARM_MASK = 0x1fff VOID = 0x20000000 OUT = 0x40000000 IN = 0x80000000 INOUT = IN|OUT DIRMASK = 0xe0000000 def PARM_LEN(x) (x >> 16) & PARM_MASK end def BASECMD(x) x & ~(PARM_MASK << 16) end def GROUP(x) (x >> 8) & 0xff end def _IOC(inout, group, num, len) inout | ((len & PARM_MASK) << 16) | ((group.is_a?(String) ? group[0] : group) << 8) | num end def _IO(g, n) _IOC(VOID, g, n, 0) end def _IOR(g, n, t) _IOC(OUT, g, n, t) end def _IOW(g, n, t) _IOC(IN, g, n, t) end def _IOWR(g, n, t) _IOC(INOUT, g, n, t) end module_function :PARM_LEN, :BASECMD, :GROUP module_function :_IOC, :_IO, :_IOR, :_IOW, :_IOWR end module DISK class << self include IOCCOM end DIOCGMEDIASIZE = _IOR('d', 129, 8) end module CDIO class << self include IOCCOM end CDIOCSTART = _IO('c', 22) CDIOCEJECT = _IO('c', 24) CDIOCCLOSE = _IO('c', 28) sizeof_ioc_toc_header = [0, 0, 0].pack('SC2').size() CDIOREADTOCHEADER = _IOR('c', 4, sizeof_ioc_toc_header) # XXX struct with bitfield black magic art sizeof_cd_toc_entry = [0, 0, 0].pack('xC2xN').size() sizeof_ioc_read_toc_single_entry = [0, 0].pack('C2x2').size() + sizeof_cd_toc_entry CDIOREADTOCENTRY = _IOWR('c', 6, sizeof_ioc_read_toc_single_entry) sizeof_ioc_read_toc_entry = [0, 0, 0, "\0"].pack('C2SP').size() CDIOREADTOCENTRYS = _IOWR('c', 5, sizeof_ioc_read_toc_entry) CD_LBA_FORMAT = 1 end module CDRIO class << self include IOCCOM end CDR_DB_RAW = 0x0 CDR_DB_RAW_PQ = 0x1 CDR_DB_RAW_PW = 0x2 CDR_DB_RAW_PW_R = 0x3 CDR_DB_RES_4 = 0x4 CDR_DB_RES_5 = 0x5 CDR_DB_RES_6 = 0x6 CDR_DB_VS_7 = 0x7 CDR_DB_ROM_MODE1 = 0x8 CDR_DB_ROM_MODE2 = 0x9 CDR_DB_XA_MODE1 = 0xa CDR_DB_XA_MODE2_F1 = 0xb CDR_DB_XA_MODE2_F2 = 0xc CDR_DB_XA_MODE2_MIX = 0xd CDR_DB_RES_14 = 0xe CDR_DB_VS_15 = 0xf CDR_SESS_CDROM = 0x00 CDR_SESS_CDI = 0x10 CDR_SESS_CDROM_XA = 0x20 CDR_SESS_NONE = 0x00 CDR_SESS_FINAL = 0x01 CDR_SESS_RESERVED = 0x02 CDR_SESS_MULTI = 0x03 sizeof_int = [0].pack('i').size() sizeof_cdr_track = [0, 0, 0].pack('i3').size() sizeof_cdr_cuesheet = [0, "\0", 0, 0, 0].pack('iPi3').size() CDRIOCBLANK = _IOW('c', 100, sizeof_int) CDR_B_ALL = 0x0 CDR_B_MIN = 0x1 CDR_B_SESSION = 0x6 CDIOCRESET = _IO('c', 21) CDRIOCNEXTWRITEABLEADDR = _IOR('c', 101, sizeof_int) CDRIOCINITWRITER = _IOW('c', 102, sizeof_int) CDRIOCINITTRACK = _IOW('c', 103, sizeof_cdr_track) CDRIOCSENDCUE = _IOW('c', 104, sizeof_cdr_cuesheet) CDRIOCFLUSH = _IO('c', 105) CDRIOCFIXATE = _IOW('c', 106, sizeof_int) CDRIOCREADSPEED = _IOW('c', 107, sizeof_int) CDRIOCWRITESPEED = _IOW('c', 108, sizeof_int) CDRIOCGETBLOCKSIZE = _IOR('c', 109, sizeof_int) CDRIOCSETBLOCKSIZE = _IOW('c', 110, sizeof_int) CDRIOCGETPROGRESS = _IOR('c', 111, sizeof_int) CDR_MAX_SPEED = 0xffff end class Burncd include CDIO include CDRIO include DISK BLOCKS = 16 BT2CTL = [ 0x0, -1, -1, -1, -1, -1, -1, -1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, -1, -1 ] BT2DF = [ 0x0, -1, -1, -1, -1, -1, -1, -1, 0x10, 0x30, 0x20, -1, 0x21, -1, -1, -1 ] class InvalidWave < StandardError end class TrackInfo attr_accessor :fd, :name, :size, :block_size, :block_type attr_accessor :pregap, :addr def roundup_blocks (@size + @block_size - 1) / @block_size end end def initialize(config) @do_cleanup = false @config = config @do_confirm = @config[:confirm] @fd = File.open(@config[:device], "wb+") @saved_block_size = [0].pack('i') raise RuntimeError, 'ioctl(CDRIOGETBLOCKSIZE)' \ if @fd.ioctl(CDRIOCGETBLOCKSIZE, @saved_block_size) < 0 @saved_block_size = @saved_block_size.unpack('i')[0] raise RuntimeError, 'ioctl(CDRIOCWRITESPEED)' \ if @fd.ioctl(CDRIOCWRITESPEED, [config[:speed]].pack('i')) < 0 @do_cleanup = true @tracks = [] @block_type = nil @block_size = nil @wave_check = false @done_stdin = false @cdopen = false @vcdbin = false end attr_accessor :do_cleanup attr_reader :block_type, :block_size, :wave_check def track_type=(type) case type when :raw @block_type = CDR_DB_RAW @block_size = 2352 @wave_check = false when :audio @block_type = CDR_DB_RAW @block_size = 2352 @wave_check = true when :data, :mode1 @block_type = CDR_DB_ROM_MODE1 @block_size = 2048 @wave_check = false when :mode2 @block_type = CDR_DB_ROM_MODE2 @block_size = 2336 @wave_check = false when :xamode1 @block_type = CDR_DB_XA_MODE1 @block_size = 2048 @wave_check = false when :xamode2 @block_type = CDR_DB_XA_MODE2_F2 @block_size = 2324 @wave_check = false when :vcd @block_type = CDR_DB_XA_MODE2_F2; @block_size = 2352 @config[:dao] = true @config[:nogap] = true @wave_check = false when :vcdbin @block_type = CDR_DB_XA_MODE2_F2; @block_size = 2352 @config[:dao] = true @config[:nogap] = false @wave_check = false @vcdbin = true end end def add_track(file) if @vcdbin raise ArgumentError, "Already have enough file for vcdbin" \ if @vcdbin.is_a?(String) @vcdbin = file @tracks.clear() track = TrackInfo.new() track.name = @vcdbin track.pregap = 150 track.block_size = @block_size track.block_type = @block_type track.size = 300 * @block_size @tracks.push(track) track = TrackInfo.new() track.name = @vcdbin track.pregap = 150 track.block_size = @block_size track.block_type = @block_type track.size = File.size(@vcdbin) - (300 * @block_size) - (150 * @block_size) @tracks.push(track) else track = TrackInfo.new() if file == '-' if @done_stdin STDERR.puts 'skipping multiple usages of stdin' return else track.fd = STDIN track.size = -1 @done_stdin = true end else track.fd = File.open(file, "rb") track.size = File.size(file) raise IOError, 'track.size < 0 ?' if track.size < 0 end track.name = file track.block_size = @block_size track.block_type = @block_type # # XXX something fishy about cdrecord dao mixmode # Only usefull in DAO #track.pregap = (@tracks.empty? || (@tracks[-1].block_type == track.block_type)) ? 150 : 255 track.pregap = (@tracks.empty? || (@tracks[-1].block_type != track.block_type)) ? 150 : 0 _setup_wavefile(track) \ if @wave_check && track.size != -1 && /\.wav$/i =~ track.name if @config[:verbose] roundup_blocks = track.roundup_blocks() STDERR.printf( "adding type 0x%02x file %s size %d KB %d blocks %s\n", track.block_type, track.name, track.size/1024, roundup_blocks, ((track.size / track.block_size) != roundup_blocks) ? "(0 padded)" : "" ); end @tracks.push(track) end end def _setup_wavefile(track) begin header = track.fd.sysread(44) raise InvalidWave unless header.size() == 44 header = header.unpack('a4Ia4a4Is2I2s2a4I') raise InvalidWave unless header[0] == 'RIFF' && header[2] == 'WAVE' && header[3] == 'fmt ' && header[6] == 2 && header[7] == 44100 && header[10] == 16 track.size -= 44 rescue InvalidWave track.fd.sysseek(0, IO::SEEK_SET) end end def burn unless @tracks.empty? raise RuntimeError, 'ioctl(CDIOCSTART)' \ if @fd.ioctl(CDIOCSTART, [0].pack('i')) < 0 raise RuntimeError, 'ioctl(CDRIOCINITWRITER)' \ if @fd.ioctl(CDRIOCINITWRITER, [@config[:test_write] ? 1 : 0].pack('i')) < 0 if @config[:dao] do_DAO() else do_TAO() end end end def lba2msf(lba) lba += 150 lba &= 0xffffff [lba / (60 * 75), (lba % (60 * 75)) / 75, (lba % (60 * 75)) % 75] end def msf2lba(min, sec, frm) (((min * 60) + sec) * 75) + frm - 150 end private :lba2msf, :msf2lba def diskinfo # XXX sizeof(off_t) == 8, but ruby cannot deal with that! ret = "\0"*8 raise RuntimeError, 'ioctl(DIOCGMEDIASIZE)' \ if @fd.ioctl(DIOCGMEDIASIZE, ret) < 0 ret = ret.unpack('L')[0] if ret % 2352 == 0 blk = ret / 2352 elsif ret % 2048 == 0 blk = ret / 2048 else raise RuntimeError, 'ioctl(DIOCGMEDIASIZE) : Unknown blocksize' end m, s, f = lba2msf(blk - 150) STDOUT.printf("Total capacity: %02d:%02d:%02d (%d blocks, %d/%d MB)\n", m, s, f, blk, (blk * 2048) / 1024 / 1024, (blk * 2352) / 1024 / 1024) end def info hdr = [0, 0, 0].pack('SC2') raise RuntimeError, 'ioctl(CDIOREADTOCHEADER)' \ if @fd.ioctl(CDIOREADTOCHEADER, hdr) < 0 hdr = hdr.unpack('SC2') ntocentries = hdr[2] - hdr[1] + 1 toc_buffer = [0, 0, 0].pack('xC2xN') * 100 sizeof_cd_toc_entry = toc_buffer.size() / 100 toc_ent = [ CD_LBA_FORMAT, 0, (ntocentries + 1) * sizeof_cd_toc_entry, toc_buffer ].pack('C2SP') raise RuntimeError, 'ioctl(CDIOREADTOCENTRYS)' \ if @fd.ioctl(CDIOREADTOCENTRYS, toc_ent) < 0 tracks = [] 0.upto(ntocentries) do |i| ent = toc_buffer.slice!(0, sizeof_cd_toc_entry) bits = ent.slice!(0, 2)[1 .. -1].unpack('C')[0] is_data = ((bits & ~((bits >> 4) << 4)) & 4) != 0 track, lba = ent.unpack('CxN') is_leadout = (i == ntocentries) if i > 0 if is_data && !tracks[-1][2] && !is_leadout # XXX Possible CD-EXTRA tracks[-1] << (lba - (152 * 75)) else tracks[-1] << lba tracks[-1][-1] -= 150 if is_data != tracks[-1][2] && !is_leadout end end tracks.push([track, lba, is_data, is_leadout]) end STDOUT.printf( "Starting track = %d, ending track = %d, TOC size = %d bytes\n", hdr[1], hdr[2], hdr[0] ) STDOUT.puts 'track start duration block length type' STDOUT.puts '-------------------------------------------------' last_type = nil tracks.each do |num, lba_start, is_data, is_leadout, lba_end| m, s, f = lba2msf(lba_start) if is_leadout STDOUT.printf( "%5d %2d:%02d.%02d - %6d - -\n", num, m, s, f, lba_start ) else len = lba_end - lba_start dm, ds, df = lba2msf(len - 150) STDOUT.printf( "%5d %2d:%02d.%02d %2d:%02d.%02d %6d %6d %5s\n", num, m, s, f, dm, ds, df, lba_start, len, is_data ? 'data' : 'audio' ) end end end def msinfo # XXX raise RuntimeError, 'ioctl(CDRIOCINITTRACK)' \ if @fd.ioctl(CDRIOCINITTRACK, [0, 0, 0].pack('i3')) < 0 header = [0, 0, 0].pack('SC2') raise RuntimeError, 'ioctl(CDIOREADTOCHEADER)' \ if @fd.ioctl(CDIOREADTOCHEADER, header) < 0 single_entry = [ CD_LBA_FORMAT, header.unpack('SC2')[2], 0, 0, 0 ].pack('C2x3C2xN') raise RuntimeError, 'ioctl(CDIOREADTOCENTRY)' \ if @fd.ioctl(CDIOREADTOCENTRY, single_entry) < 0 addr = [0].pack('i') raise RuntimeError, 'ioctl(CDRIOCNEXTWRITEABLEADDR)' \ if @fd.ioctl(CDRIOCNEXTWRITEABLEADDR, addr) < 0 lasttrack = single_entry.unpack('C2x3C2xN') if ((lasttrack[2] & ~((lasttrack[2] >> 4) << 4)) & 4) != 0 lastaddr = lasttrack[4] else lastaddr = 0 end STDOUT.puts "#{lastaddr},#{addr.unpack('i')[0]}" end def fixate if @config[:fixate] && !@config[:dao] confirm() STDERR.puts 'fixating CD, please wait..' unless @config[:quiet] raise RuntimeError, 'ioctl(CDRIOCFIXATE)' \ if @fd.ioctl(CDRIOCFIXATE, [@config[:multi] ? 1 : 0].pack('i')) < 0 end end def eject if @config[:eject] raise RuntimeError, 'ioctl(CDIOCEJECT)' \ if @fd.ioctl(CDIOCEJECT) < 0 end if @config[:close] sleep(0.5) raise RuntimeError, 'ioctl(CDIOCCLOSE)' \ if @fd.ioctl(CDIOCCLOSE) < 0 end end def cleanup @fd.ioctl(CDRIOCSETBLOCKSIZE, [@saved_block_size].pack('i')) \ if @do_cleanup && !@fd.closed? && @saved_block_size.is_a?(Integer) @do_cleanup = false end def close @fd.close() unless @fd.closed? @tracks.each do |track| track.fd.close() if track.fd && !track.fd.closed? end end def erase _erase_blank(CDR_B_ALL) end def blank _erase_blank(CDR_B_MIN) end def _erase_blank(blank) confirm() quiet = @config[:quiet] task = (blank == CDR_B_ALL) ? 'eras' : 'blank' STDERR.printf("%sing CD, please wait..\r", task) unless quiet raise RuntimeError, 'ioctl(CDRIOCBLANK)' \ if @fd.ioctl(CDRIOCBLANK, [blank].pack('i')) < 0 #percent = [0].pack('i') #_percent = nil #error = 0 #_last = 0 ind = %w[- \\ | /] idx = 0 while true sleep(0.25) begin raise Errno::EBUSY if idx % 4 != 0 error = @fd.ioctl(CDIOCRESET) rescue Errno::EBUSY error = -1 end if error == 0 STDERR.printf("%sing CD - %s DONE \r", task, ind[idx % 4]) \ unless quiet break else STDERR.printf("%sing CD - %s \r", task, ind[idx % 4]) \ unless quiet end idx += 1 #error = @fd.ioctl(CDRIOCGETPROGRESS, percent) #_percent = percent.unpack('i')[0] #_percent = 0 unless _percent.is_a?(Integer) #STDERR.printf("%sing CD - %d %% done \r", task, _percent) \ # if _percent > 0 && !quiet #break if error != 0 || _percent == 100 || (_percent == 0 && _last > 90) #_last = _percent end STDERR.print "\n" end def cue_ent(ctl, adr, track, idx, dataform, scms, lba) lba += 150 # litle-endian, unsigned 4Bit truncation [ ((ctl & 0xf) << 4) | (adr & 0xf), track, idx, dataform, scms, lba / (60 * 75), (lba % (60 * 75)) / 75, (lba % (60 * 75)) % 75 ].pack('C8') end def do_DAO verbose = @config[:verbose] cdformat = CDR_SESS_CDROM j = 0 cue = '' STDERR.puts 'Burning mode: DAO' if verbose addr = [0].pack('i') raise RuntimeError, 'ioctl(CDRIOCNEXTWRITEABLEADDR)' \ if @fd.ioctl(CDRIOCNEXTWRITEABLEADDR, addr) < 0 addr = addr.unpack('i')[0] STDERR.puts "next writeable LBA #{addr}" if verbose if addr != -150 STDERR.puts "resetting next writable LBA!" if verbose addr = -150 end prevtrack = @tracks[0] cue << cue_ent( BT2CTL[prevtrack.block_type], 0x01, 0x00, 0x0, (BT2DF[prevtrack.block_type] & 0xf0) | (prevtrack.block_type < 8 ? 0x01 : 0x04), 0x00, addr ) @tracks.each_with_index do |track, i| raise RuntimeError, 'track type not supported in DAO mode' \ if BT2CTL[track.block_type] < 0 || BT2DF[track.block_type] < 0 cdformat = CDR_SESS_CDROM_XA \ if track.block_type >= CDR_DB_XA_MODE1 if track.pregap > 0 cue << cue_ent( BT2CTL[track.block_type], 0x01, i + 1, 0x0, BT2DF[track.block_type], 0x00, addr ) addr += track.pregap end track.addr = addr STDERR.puts "track #{i + 1}: addr=#{track.addr} pregap=#{track.pregap}" \ if verbose cue << cue_ent( BT2CTL[track.block_type], 0x01, i + 1, 0x1, BT2DF[track.block_type], 0x00, addr ) addr += track.roundup_blocks() prevtrack = track end cue << cue_ent( BT2CTL[prevtrack.block_type], 0x01, 0xaa, 0x01, (BT2DF[prevtrack.block_type] & 0xf0) | (prevtrack.block_type < 8 ? 0x01 : 0x04), 0x00, addr ) cuesheet = [ cue.size(), cue, cdformat, @config[:multi] ? CDR_SESS_MULTI : CDR_SESS_NONE, @config[:test_write] ? 1 : 0 ].pack('iPi3') if verbose # XXX STDERR.print 'CUE sheet:' cue.unpack("C#{cue.size()}").each_with_index do |val, i| if i % 8 == 0 STDERR.printf("\n %02X", val) else STDERR.printf(" %02X", val) end end STDERR.printf("\n") end confirm() raise RuntimeError, 'ioctl(CDRIOCSENDCUE)' \ if @fd.ioctl(CDRIOCSENDCUE, cuesheet) < 0 prevtrack = nil retry_pregap = -1 paddr = nil buf = '' begin if @vcdbin @tracks.clear() track = TrackInfo.new() track.name = @vcdbin track.fd = File.open(@vcdbin, "rb") track.block_size = @block_size track.block_type = @block_type track.pregap = 150 track.addr = 0 track.size = File.size(@vcdbin) @tracks.push(track) end @tracks.each_with_index do |track, i| if track.pregap > 0 raise RuntimeError, 'ioctl(CDRIOCSETBLOCKSIZE)' \ if @fd.ioctl(CDRIOCSETBLOCKSIZE, [track.block_size].pack('i')) < 0 total = track.pregap * track.block_size paddr = track.addr - track.pregap @fd.sysseek(paddr * track.block_size, IO::SEEK_SET) retry_pregap = total if i == 0 && paddr < 0 STDERR.puts "writing pregap addr = #{paddr} total = #{track.pregap} blocks / #{total} bytes" \ if verbose while total > 0 buf.replace("\0"*MIN(track.block_size * BLOCKS, total)) # XXX workaround for FreeBSD 5+ GEOM !@#$%^&* begin write_size = @fd.syswrite(buf) rescue Errno::EIO => exp if retry_pregap == total @fd.sysseek(track.addr * track.block_size, IO::SEEK_SET) retry_pregap = -1 retry end raise exp end if buf.size() != write_size STDERR.puts "pregap: only wrote #{write_size} of #{buf.size} bytes" break end total -= write_size end end raise RuntimeError, 'write_file' unless write_file(track) prevtrack = track end rescue @fd.ioctl(CDRIOCFLUSH) raise end @fd.ioctl(CDRIOCFLUSH) end def do_TAO STDERR.puts 'Burning mode: TAO' if @config[:verbose] confirm() quiet = @config[:quiet] addr = [0].pack('i') @tracks.each do |track| raise RuntimeError, 'ioctl(CDRIOCINITTRACK)' \ if @fd.ioctl( CDRIOCINITTRACK, [ track.block_type, @config[:preemp] ? 1 : 0, @config[:test_write] ? 1 : 0 ].pack('i3') ) < 0 raise RuntimeError, 'ioctl(CDRIOCNEXTWRITEABLEADDR)' \ if @fd.ioctl(CDRIOCNEXTWRITEABLEADDR, addr) < 0 track.addr = addr.unpack('i')[0] track.addr = 0 unless track.addr.is_a?(Integer) STDERR.puts "next writeable LBA #{track.addr}" unless quiet begin write_file(track) rescue @fd.ioctl(CDRIOCFLUSH) raise end raise RuntimeError, 'ioctl(CDRIOCFLUSH)' \ if @fd.ioctl(CDRIOCFLUSH) < 0 end end def write_file(track) @tot_size ||= 0 quiet = @config[:quiet] filesize = track.size / 1024 raise RuntimeError, 'ioctl(CDRIOCSETBLOCKSIZE)' \ if @fd.ioctl(CDRIOCSETBLOCKSIZE, [track.block_size].pack('i')) < 0 begin @fd.sysseek(track.addr * track.block_size, IO::SEEK_SET) \ if track.addr >= 0 rescue end STDERR.puts "addr = #{track.addr} size = #{track.size} blocks = #{track.roundup_blocks()}" \ if @config[:verbose] unless quiet if track.fd == STDIN STDERR.puts 'writing from stdin' else STDERR.puts "writing from file #{track.name} size #{filesize} KB" end end size = 0 count = 0 buf = '' res = 0 mod = 0 read_size = (track.size == -1) ? track.block_size * BLOCKS : 0 begin while true read_size = MIN(track.size - size, track.block_size * BLOCKS) \ if track.size != -1 buf.replace(track.fd.sysread(read_size)) count = buf.size() raise IOError, "sysread() < 1" if count < 1 mod = count % track.block_size if mod != 0 buf << "\0"*(track.block_size - mod) count = buf.size() end begin res = @fd.syswrite(buf) raise IOError unless res == count rescue => err STDERR.print "\nonly wrote #{res || 0} of #{count} bytes err=#{err.class}\n" raise EOFError end size += count @tot_size += count unless quiet STDERR.print "written this track #{size / 1024} KB" STDERR.print " (#{(size / 1024) * 100 / filesize}%)" \ if track.size != -1 && filesize > 0 STDERR.print " total #{@tot_size / 1024} KB\r" end break if track.size != -1 && size >= track.size end rescue EOFError raise if track.size != -1 end STDERR.print "\n" unless quiet track.fd.close() true end def confirm if @do_confirm #STDERR.print 'Starting real write in 10 seconds. Press Ctrl+C to abort.' #9.downto(0) do |i| # sleep(1) # STDERR.print "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b #{i} seconds. Press Ctrl+C to abort." #end 10.downto(0) do |i| STDERR.printf("Starting real write in %2d seconds. Press Ctrl+C to abort.\r", i) sleep(1) end STDERR.print "\n" @do_confirm = false end end private :_erase_blank, :_setup_wavefile private :do_DAO, :do_TAO, :write_file, :cue_ent end config = { :dao => false, :eject => false, :close => false, :device => ENV['CDROM'] || '/dev/acd0', :list => nil, :confirm => true, :multi => false, :nogap => false, :preemp => false, :quiet => false, :speed => 4, :test_write => false, :fixate => false, :verbose => false } def usage STDERR.print <<-EOF Usage: #{File.basename($0)} [-cdelmnpqtvy] [-f device] [-s speed] [command] [command file ...] EOF exit(1) end def fatal(status, msg) STDERR.puts "#{File.basename($0)}: #{msg}" exit(status) end parser = GetoptLong.new() parser.quiet = true parser.set_options( ['-c', GetoptLong::NO_ARGUMENT], ['-d', GetoptLong::NO_ARGUMENT], ['-e', GetoptLong::NO_ARGUMENT], ['-l', GetoptLong::NO_ARGUMENT], ['-m', GetoptLong::NO_ARGUMENT], ['-n', GetoptLong::NO_ARGUMENT], ['-p', GetoptLong::NO_ARGUMENT], ['-q', GetoptLong::NO_ARGUMENT], ['-t', GetoptLong::NO_ARGUMENT], ['-v', GetoptLong::NO_ARGUMENT], ['-y', GetoptLong::NO_ARGUMENT], ['-f', GetoptLong::REQUIRED_ARGUMENT], ['-s', GetoptLong::REQUIRED_ARGUMENT] ) begin parser.each do |opt, arg| case opt when '-c' config[:close] = true when '-d' config[:dao] = true when '-e' config[:eject] = true when '-l' config[:list] = arg when '-m' config[:multi] = true when '-n' config[:nogap] = true when '-p' config[:preemp] = true when '-q' config[:quiet] = true when '-t' config[:test_write] = true when '-v' config[:verbose] = true when '-y' config[:confirm] = false when '-f' fatal(1, "Invalid device: #{arg}") unless File.chardev?(arg) config[:device] = arg when '-s' if arg.casecmp('max') == 0 config[:speed] = CDRIO::CDR_MAX_SPEED else config[:speed] = arg.to_i * 177 end if config[:speed] <= 0 fatal(1, "Invalid speed: #{arg}") end end end rescue GetoptLong::AmbigousOption, GetoptLong::InvalidOption, GetoptLong::MissingArgument, GetoptLong::NeedlessArgument, ArgumentError => err STDERR.puts "#{File.basename($0)}: #{err.message}" usage() end usage() if ARGV.empty? begin bcd = Burncd.new(config) ARGV.each do |arg| case arg when /^fixate$/i config[:fixate] = true break when /^msinfo$/i bcd.msinfo() break when /^diskinfo$/i bcd.diskinfo() break when /^info/i bcd.info() break when /^erase$/i bcd.erase() next when /^blank$/i bcd.blank() next when /^(raw|audio|data|(xa)?mode[12]|vcd|vcdbin)$/i bcd.track_type = arg.downcase.to_sym next end raise RuntimeError, 'no data format selected' \ unless bcd.block_type && bcd.block_size bcd.add_track(arg) end bcd.burn() bcd.fixate() bcd.cleanup() bcd.eject() bcd.close() rescue Interrupt STDERR.puts "\nAborting..." if bcd bcd.cleanup() bcd.close() end exit(1) rescue => err STDERR.puts "#{File.basename($0)}: #{err.message}" if bcd bcd.cleanup() bcd.close() end exit(1) end