ВЛАДИМИР МЕШКОВ
Запись дисков CD-R/RW в Linux
Часть 3
В третьей части статьи рассматривается порядок использования мультимедийных команд стандарта SCSI (SCSI Multi-Media Commands) для записи многосессионных CD-R/RW дисков в режиме TAO (Track-at-Once).
Работоспособность всех примеров программ была проверена для ОС Linux, ядро 2.4.28. В ядре включены режим SCSI-эмуляции для ATAPI-устройств (SCSI host adapter emulation for IDE ATAPI devices) и поддержка SCSI Generic драйвера. Использовались следующие модели приводов для чтения и записи CD-R/RW и DVD-R/RW дисков:
- TEAC CD-W524E 1.0E
- MITSUMI CR-48XATE
- NEC CD-RW NR-9400A R800
- ASUS DRW-1604P 1.09
Работа над ошибками
Прежде чем приступить к рассмотрению основного материала, устраним ошибки, допущенные во второй части статьи [5]. Проблема заключается в следующем – при попытке записи информации с использованием привода ASUS программа аварийно завершает выполнение со следующим сообщением:
lba - 320
Sense data: 0x70 0x00 0x02 0x00 0x00 0x00 0x00 0x0e 0x00 0x00
0x00 0x00 0x04 0x08 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00
Driver status=0x28
Cannot write image.iso
|
Здесь Sense Key = 0x02, ASC = 0x04, ASCQ = 0x08, что означает LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS (устройство не готово к работе, выполняется операция длинной записи). Остановка выполнения программы всегда происходит на секторе номер 320. Аналогичным образом ведут себя приводы MITSUMI и _NEC. С приводом TEAC подобной проблемы не возникало. В чем причина этой ошибки? Дело в том, что каждый привод имеет внутренний буфер для данных, и при записи информация сначала попадает в этот буфер, а затем из него переносится на диск. Буфер имеет ограниченную ёмкость, определить которую можно при помощи команды READ BUFFER CAPACITY. Формат этой команды приведён на рис. 1.
READ BUFFER CAPACITY CDB
Bit
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
Operation Code (5Ch)
|
1
|
Reserved
|
Reserved
|
BLOCK
|
2
|
Reserved
|
3
|
Reserved
|
4
|
Reserved
|
5
|
Reserved
|
6
|
Reserved
|
7
|
(MSB)
|
Allocation Length
|
8
|
(LSB)
|
9
|
Control
|
Рисунок 1. Формат команды READ BUFFER CAPACITY
Если поле BLOCK установлено в 0, устройство вернёт блок данных следующего формата (рис. 2):
Buffer Capacity Structure, when Block = 0
Bit
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
(MSB)
|
Data Length
|
1
|
(LSB)
|
2
|
Reserved
|
3
|
Reserved
|
4
|
(MSB)
Length of the Buffer
(LSB)
|
5
|
6
|
7
|
8
|
(MSB)
Available Length of Buffer
(LSB)
|
9
|
10
|
11
|
Рисунок 2. Формат блока данных о буфере устройства, BLOCK = 0
Здесь Length of the Buffer – размер буфера в байтах, Available Length of Buffer – размер неиспользуемой (доступной) области буфера в байтах.
Модифицируем листинг из второй части – при записи информации будем контролировать размер доступной области внутреннего буфера устройства при помощи функции read_buff_cap():
__u32 read_buff_cap()
{
__u8 read_buff_cap_cmd[10];
__u8 data_buff[12];
__u32 buff_alen = 0;
memset(read_buff_cap_cmd, 0, 10);
read_buff_cap_cmd[0] = 0x5C;
read_buff_cap_cmd[8] = 0x0C;
send_cmd(read_buff_cap_cmd, 10, SG_DXFER_FROM_DEV, data_buff,0x0C, 20);
memcpy((void *)&buff_alen, data_buff + 8, 4);
buff_alen = __swab32(buff_alen);
printf("\tAvailable length - %u\n", buff_alen);
return buff_alen;
}
Контроль за размером доступной области буфера будет выполняться в функции write_iso, в цикле записи информации (полный листинг этой функции находится в [5]):
int write_iso(__u8 *file_name)
{
....
while(read(in_f, write_buff, CD_FRAMESIZE) > 0) {
read_buff_cap();
printf("lba - %6d", lba1);
lba = __swab32(lba1);
memcpy((write_cmd + 2), (void *)&lba, 4);
lba1 += 1;
send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, CD_FRAMESIZE, 20);
}
return 0;
}
Результат работы программы (привод ASUS):
Available length - 1267712
lba - 0 Available length - 1175552
lba - 1 Available length - 1173504
lba - 2 Available length - 1171456
........
lba - 317 Available length - 526336
lba - 318 Available length - 524288
lba - 319 Available length - 0
lba - 320
Sense data: 0x70 0x00 0x02 0x00 0x00 0x00 0x00 0x0e 0x00 0x00
0x00 0x00 0x04 0x08 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00
Driver status=0x28
Cannot write image.iso
|
Как только доступная область буфера становится равной нулю, устройство на попытку передачи ему информации отвечает LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS. Один из вариантов решения данной проблемы – подождать, пока буфер устройства освободится, а затем продолжить запись, например:
while(read_buff_cap() == 0) continue;
Такой вариант устраивает ASUS, но MITSUMI и _NEC так просто не сдаются и, попав в цикл, не хотят его покидать. Спецификация SFF8090i [1] при возникновении такого рода ошибки требует повторить команду записи (см. п.14.48 WRITE(10) command):
While writing is occurring, if WRITE (10) command or WRITE (12) command cannot be terminated immediately due to insufficient buffer
capacity, the logical unit may terminate the WRITE command with CHECK CONDITION status, 2/04/08 LOGICAL UNIT NOT READY, LONG WRITE
IN PROGRESS and the host shall issue the same WRITE command again. After logical unit becomes ready due to sufficient buffer capacity
for the WRITE command, the WRITE command shall be performed normally.
|
Именно таким образом поступает dvdrecord. Запустив его с ключом -v, мы увидим следующую картину:
Executing "write_g1" command on Bus 0 Target 1,
Lun 0 timeout 200s
CDB: 2A 00 00 00 3A 01 00 00 1F 00
dvdrecord: Input/Output error, write_g1: scsi sendcmd: no error
CDB: 2A 00 00 00 3A 01 00 00 1F 00
Status: 0x2 (CHECK CONDITION)
Sense buffer: 70 00 02 00 00 00 00 0E 00 00 00 00
04 08 00 00
Sense Key: 0x2 Not Ready, Segment 0
Sense Code: 0x04 Qual 0x08 (logical unit not ready,
Long write in progress) Fru 0x0
Sense flags: BLK 0 (not valid)
cmd finished after 0x000 s timeout 200s
Executing "write_g1" command on Bus 0 Target 1,
Lun 0 timeout 200s
CDB: 2A 00 00 00 3A 01 00 00 1F 00
cmd finished after 0x000 s timeout 200s
|
CDB – это Command Descriptor Block (дескриптор командного блока), 2A – код команды WRITE_10. Зафиксировав ошибку LONG WRITE IN PROGRESS, программа dvdrecord в соответствии с требованием спецификации повторно посылает устройству команду записи.
Исходя из вышеизложенного, внесём исправления в текст программы для записи ISO-образа на CD-R/RW диск. Изменениям подвергнутся функция посылки пакетной команды устройству send_cmd() и функция записи write_iso:
int send_cmd(__u8 *cmd, __u8 cmdlen, int direction, __u8 *data, __u32 datalen, unsigned int timeout)
{
sg_io_hdr_t io_hdr;
__u8 sense_buffer[32];
#define SK sense_buffer[2]
#define ASC sense_buffer[12]
#define ASCQ sense_buffer[13]
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = cmdlen;
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = direction;
io_hdr.dxfer_len = datalen; // размер данных
io_hdr.dxferp = data; // указатель на блок данных
io_hdr.cmdp = cmd;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = timeout * 1000;
if(ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO ioctl");
return -1;
}
if((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
if(io_hdr.sb_len_wr > 0) {
/* Если SK/ASC/ASCQ == 02/04/08 (Not Ready. Long Write in Progress), то необходимо повторить команду WRITE_10 */
if((SK == NOT_READY) && (ASC == 0x04) && (ASCQ == 0x08)) return 1;
syslog(LOG_INFO,"Command: 0x%02X", io_hdr.cmdp[0]);
syslog(LOG_INFO,"Sense data (SK/ASC/ASCQ): 0x%02X/0x%02X/0x%02X",
SK,ASC,ASCQ);
}
if(io_hdr.masked_status)
syslog(LOG_INFO, "SCSI status = 0x%x\n", io_hdr.status);
if(io_hdr.host_status)
syslog(LOG_INFO, "Host status = 0x%x\n", io_hdr.host_status);
if(io_hdr.driver_status)
syslog(LOG_INFO, "Driver status = 0x%x\n", io_hdr.driver_status);
return -1;
}
return 0;
}
Текст функции write_iso() целиком приводить не будем, рассмотрим только ключевой фрагмент – посылку устройству командного пакета:
while(1) {
ret = send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, BUFF_SIZE, 20);
if(ret == 1) continue; // LONG WRITE IN PROGRESS
if(ret == 0) break;
if(ret < 0) return -1; // ошибка записи!
}
Команда WRITE_10 будет посылаться устройству до тех пор, пока не будет успешно выполнена или не произойдет ошибка, отличная от SK/ASC/ASCQ = 02/04/08 «NOT READY. LONG WRITE IN PROGRESS».
Исходный текст утилиты, выполняющей запись данных на CD-R/RW диск находится по адресу http://bob.netport.com. ua/iso2сd.tar.gz. На этом закончим изучение односессионных CD-дисков и рассмотрим, что нужно сделать для того, чтобы создать многосессионный диск.
Запись многосессионного диска
Для создания многосессионного диска необходимо присвоить полю Multi-session страницы параметров режима записи значение 11b (см. [1, 2]). В этом случае при формировании Lead-In-области указатель B0 таблицы содержания диска (Table of Contents, TOC) будет содержать координаты начала следующей области программ (формат TOC и пример чтения были рассмотрены в [3]). Формирование образа, который будет записан первым, ничем не отличается от формирования образа для односессионного диска. Каждый новый записываемый образ должен содержать ссылку на предыдущий, чтобы драйвер файловой системы мог собрать содержимое всех сессий в единое целое. Для этого при формировании ISO-образа (кроме первого) в опциях утилиты mkisofs необходимо указать стартовые координаты первого трека последней сессии и координаты следующей возможной области программ. Назовем эти координаты A и B. При помощи cdrecord значения A и B определяются следующим образом:
cdrecord -dev=X,Y,Z -msinfo
где X, Y и Z – это координаты устройства (msinfo – это сокращение от multi-session information). После выполнения команды мы получим два числа – искомые стартовые координаты. Эти числа необходимо указать в соответствующих опциях mkisofs (опция -C, см. man mkisofs) при подготовке образа для записи.
Рассмотрим два способа определения значений A и B. Оба способа подразумевают чтение таблицы содержания диска (TOC), только в первом случае мы читаем «сырую» таблицу (RAW TOC), в во втором – информацию о сессиях.
При помощи функции read_raw_toc() прочитаем RAW TOC:
/* Формат записи TOC (см. [1], [2]) */
typedef struct {
__u8 snum; // Session Number
__u8 ctrl :4; // Control
__u8 adr :4; // ADR
__u8 tno; // TNO (always 0)
__u8 point; // POINT
__u8 min; // AMIN
__u8 sec; // ASEC
__u8 frame; // AFRAME
__u8 zero; // 0
__u8 pmin; // PMIN
__u8 psec; // PSEC
__u8 pframe; // PFRAME
} toc_t;
int read_raw_toc()
{
int i;
__u8 read_toc_cmd[10];
__u8 *data_buff; // результаты чтения TOC
__u16 toc_data_len = 0; // длина записей TOC
__u32 lba;
int toc_entries = 0; // число записей TOC
int last = 0; // номер последней сессии
toc_t *t;
data_buff = (__u8 *)malloc(0xFFFF);
memset(data_buff, 0, 0xFFFF);
memset(read_toc_cmd, 0, 10);
read_toc_cmd[0] = READ_TOC;
read_toc_cmd[2] = 2; // Format Field = 10b - RAW TOC
read_toc_cmd[7] = 0xFF;
read_toc_cmd[8] = 0xFF;
send_cmd(read_toc_cmd, 10, SG_DXFER_FROM_DEV, data_buff, 0xFFFF, 20);
/* Размер TOC */
*((__u8 *)&toc_data_len) = data_buff[1];
*((__u8 *)&toc_data_len + 1) = data_buff[0];
/* Число записей TOC */
toc_entries = (toc_data_len - 2)/11;
/* Число сессий */
last = data_buff[3];
printf("Число сессий на диске - %d\n", last);
t = (toc_t *)malloc(toc_data_len);
memset((void *)t, 0, toc_data_len);
memcpy((void *)t, data_buff + 4, toc_data_len);
free(data_buff);
printf("Entry\tSession\tPoint\tMin\tSec\tFrame\tPMin\tPsec\tPFrame\tLBA\n");
for(i = 0; i < toc_entries; i++) {
printf("%d\t", i);
printf("%d\t", (t + i)->snum);
printf("%X\t", (t + i)->point);
printf("%d\t", (t + i)->min);
printf("%d\t", (t + i)->sec);
printf("%d\t", (t + i)->frame);
printf("%d\t", (t + i)->pmin);
printf("%d\t", (t + i)->psec);
printf("%d\t", (t + i)->pframe);
#define PMIN(i) (t + i)->pmin
#define PSEC(i) (t + i)->psec
#define PFRAME(i) (t + i)->pframe
#define MIN(i) (t + i)->min
#define SEC(i) (t + i)->sec
#define FRAME(i) (t + i)->frame
#define POINT(i) (t + i)->point
if((POINT(i) == 0xC0) || (POINT(i) == 0xC1)) {
printf("\n");
continue;
}
/* Координаты начала следующей возможной области программ содержит указатель B0
* в полях Min/Sec/Frame. Переводим эти координаты в LBA-формат, с учетом того, что
* перед треком находится Pre-gap область размером 150 секторов
*/
if(POINT(i) == 0xB0) lba = MSF2LBA(MIN(i), SEC(i), FRAME(i)) + 150;
else lba = MSF2LBA(PMIN(i), PSEC(i), PFRAME(i));
printf("%u\n", lba);
}
free(t);
return 0;
}
Пересчёт координат из MSF-формата в LBA выполняет макрос MSF2LBA следующего вида:
#define MSF2LBA(Min, Sec, Frame) (((Min * 60 + Sec) * 75 + Frame) - 150)
Теперь возьмём диск, на котором записано три сессии, и посмотрим на результат работы функции read_raw_toc():
Число сессий на диске - 3
Entry
|
Sess
|
Point
|
Min
|
Sec
|
Frame
|
PMin
|
Psec
|
PFrame
|
LBA
|
0
|
1
|
A0
|
0
|
0
|
0
|
1
|
0
|
0
|
4350
|
1
|
1
|
A1
|
0
|
0
|
0
|
1
|
0
|
0
|
4350
|
2
|
1
|
A2
|
0
|
0
|
0
|
0
|
50
|
18
|
3618
|
3
|
1
|
1
|
0
|
0
|
0
|
0
|
2
|
0
|
0
|
4
|
1
|
B0
|
3
|
20
|
18
|
79
|
59
|
74
|
15018
|
5
|
1
|
C0
|
102
|
0
|
156
|
97
|
32
|
10
|
|
6
|
1
|
C1
|
72
|
52
|
176
|
0
|
0
|
0
|
|
7
|
2
|
A0
|
0
|
0
|
0
|
2
|
0
|
0
|
8850
|
8
|
2
|
A1
|
0
|
0
|
0
|
2
|
0
|
0
|
8850
|
9
|
2
|
A2
|
0
|
0
|
0
|
3
|
43
|
35
|
16610
|
10
|
2
|
2
|
0
|
0
|
0
|
3
|
22
|
18
|
15018
|
11
|
2
|
B0
|
5
|
13
|
35
|
79
|
59
|
74
|
23510
|
12
|
3
|
A0
|
0
|
0
|
0
|
3
|
0
|
0
|
13350
|
13
|
3
|
A1
|
0
|
0
|
0
|
3
|
0
|
0
|
13350
|
14
|
3
|
A2
|
0
|
0
|
0
|
5
|
43
|
59
|
25634
|
15
|
3
|
3
|
0
|
0
|
0
|
5
|
15
|
35
|
23510
|
16
|
3
|
B0
|
7
|
13
|
59
|
79
|
59
|
74
|
32534
|
Указатель A0 третьей сессии (запись 12) содержит в поле PMin номер первого трека последней сессии. Это значение равно 3. Запись 15 в полях PMin/PSec/PFrame содержит стартовые координаты этого трека – 5/15/35, или 23510 в LBA-формате. Координаты начала следующей возможной области программ содержит указатель B0 в полях Min/Sec/Frame (запись 16) – 7/13/59 (32534 в LBA-формате).
Второй способ определения значения числа A – чтение информации о сессиях. Для этого в поле Format командного блока READ TOC/PMA/ATIP (см. спецификацию [2], табл. 441) должно принять значение 1. Блок информации о сессиях имеет вид, представленный на рис. 3.
READ TOC/PMA/ATIP response data (Format = 0001b): Multi-session Information
Bit
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
(MSB)
|
TOC Data Length
|
1
|
(LSB)
|
2
|
First Complete Session Number
|
3
|
Last Complete Session Number
|
|
0
|
Reserved
|
1
|
ADR
|
CONTROL
|
2
|
First Track Number In Last Complete Session
|
3
|
Reserved
|
4
.
.
7
|
(MSB)
Start Address of First Track in Last Session
(LSB)
|
Рисунок 3. Блок информации о сессиях
Здесь First и Last Complete Session Number – номера первой и последней завершенной сессии, First Track Number In Last Complete Session – номер первого трека в последней завершенной сессии, Start Address of First Track in Last Session – стартовый адрес этого трека. Формат блока информации о сессиях описывает следующая структура:
typedef struct {
__u8 res1;
__u8 ctrl :4;
__u8 adr :4;
__u8 first_trk; // номер первого трека посл. сессии
__u8 res2;
__u32 start_addr; // стартовый адрес трека
} ms_info_t;
Чтение блока информации о сессиях выполняет функция read_ms_info():
void read_ms_info()
{
int first_trk = 0; // номер первого трека посл. сессии
__u8 read_toc_cmd[10]; // CDB - Command Descriptor Block
__u8 data_buff[12];
__u32 start_addr = 0; // стартовый адрес трека
ms_info_t *ms_info;
memset(data_buff, 0, 12);
ms_info = (void *)(data_buff + 4);
/* Формируем CDB */
memset(read_toc_cmd, 0, 10);
read_toc_cmd[0] = READ_TOC;
read_toc_cmd[2] = 1; // Format = 1, Multi-session Information
read_toc_cmd[8] = 12; // размер блока данных
send_cmd(read_toc_cmd, 10, SG_DXFER_FROM_DEV, data_buff, 12, 20);
first_trk = ms_info->first_trk;
start_addr = __swab32(ms_info->start_addr);
printf("Первый трек последней сессии - %d\n", first_trk);
printf("Стартовый адрес трека - %u\n", start_addr);
return;
}
Осталось узнать значение числа B – стартового адреса следующей возможной области программ. Этот адрес является адресом невидимого трека (invisible track), и определить его можно при помощи команды READ TRACK INFORMATION. Формат этой команды и пример использования был рассмотрен в [5]. Следующая функция выполняет чтение искомого адреса:
__u32 read_track_info(int trk_num)
{
__u8 read_track_info_cmd[10];
__u8 data_buff[40];
__u32 lba = 0;
memset(data_buff, 0, 40);
memset(read_track_info_cmd, 0, 10);
read_track_info_cmd[0] = 0x52;
read_track_info_cmd[1] = 1;
read_track_info_cmd[5] = trk_num; // 0xFF - invisible track
read_track_info_cmd[8] = 40;
send_cmd(read_track_info_cmd, 10, SG_DXFER_FROM_DEV, data_buff, 40, 20);
memcpy((void *)&lba, (void *)(data_buff + 8), 4);
return __swab32(lba);
}
В результате работы двух рассмотренных функций – read_ms_info() и read_track_info() – мы получим для имеющегося в нашем распоряжении трехсессионного диска значения чисел А и В: A = 23510 и B = 32534. Теперь можно сформировать ISO-образ для записи на диск четвертой сессии:
mkisofs -R -J -C 23510,32534 -M [имя файла устройства] -o track-04.iso [входной файл]
Перед тем как приступить к записи информации, целесообразно выяснить, в каком состоянии находится диск, т.е. можно ли дописать на него новую сессию. Эту информацию можно получить при помощи команды READ DISK INFORMATION. Формат этой команды представлен на рис. 4.
READ DISC INFORMATION CDB
Bit
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
Operation Code (51h)
|
1
|
Reserved
|
2
|
Reserved
|
3
|
Reserved
|
4
|
Reserved
|
5
|
Reserved
|
6
|
Reserved
|
7
|
(MSB)
|
Allocation Length
|
8
|
(LSB)
|
9
|
Control
|
Рисунок 4. Формат команды READ DISK INFORMATION
Формат данных, возвращаемых устройством по этой команде, представлен на рис. 5.
Необходимую нам информацию содержит поле Disk Status. Поле может принимать следующие значения:
- 00b – Empty Disk, диск пустой, не содержит информации;
- 01b – Incomplete Disk, незавершенный диск, на который можно дописывать информацию;
- 10b – Finalized Disk, диск завершен, информацию дописывать нельзя.
Поле State of last Session описывает состояние последней сессии на диске. Поле может принимать следующие значения:
- 00b – пустая сессия (Empty Session);
- 01b – незавершенная сессия (Incomplete Session);
- 11b – сессия завершена (Complete Session).
Установленный в «1» бит Erasable свидетельствует о том, что в приводе находится диск CD-RW, DVD-RAM, DVD-RW или DVD+RW.
Назначение остальных полей приведено в спецификации [2], п. 6.27 «READ DISK INFORMATION Command».
Disc Information Block
Bit
Byte
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
0
|
(MSB)
|
Disc Information Length
|
1
|
(LSB)
|
2
|
Reserved
|
Erasable
|
State of last Session
|
Disc Status
|
3
|
Number of First Track on Disc
|
4
|
Number of Sessions (Least Significant Byte)
|
5
|
First Track Number in Last Session (Least Significant Byte)
|
6
|
Last Track Number in Last Session (Least Significant Byte)
|
7
|
DID_V
|
DBC_V
|
URU
|
DAC_V
|
Reserved
|
Dbit
|
BG Format Status
|
8
|
Disc Type
|
9
|
Number of Sessions (Most Significant Byte)
|
10
|
First Track Number in Last Session (Most Significant Byte)
|
11
|
Last Track Number in Last Session (Most Significant Byte)
|
12
|
(MSB)
Disc Identification
(LSB)
|
13
|
14
|
15
|
16
|
(MSB)
Last Session Lead-in Start Address
(LSB)
|
17
|
18
|
19
|
20
|
(MSB)
Last Possible Lead-out Start Address
(LSB)
|
21
|
22
|
23
|
24
|
(MSB)
|
...
|
Disc Bar Code
|
31
|
(LSB)
|
32
|
Disc Application Code
|
33
|
Number of OPC Tables
|
32-n
|
OPC Table Entries
|
Риунок 5. Блок информации о диске, возвращаемый по команде READ DISK INFORMATION
Чтение информации о диске выполняет функция read_ disk_info():
int read_disk_info()
{
__u8 read_disk_info_cmd[10];
__u8 data_buff[34];
memset(data_buff, 0, 34);
memset(read_disk_info_cmd, 0, 10);
read_disk_info_cmd[0] = 0x51;
read_disk_info_cmd[8] = 34;
send_cmd(read_disk_info_cmd, 10, SG_DXFER_FROM_DEV, data_buff, sizeof(data_buff), 20);
switch(data_buff[2] & 3) {
case(0):
printf("Диск пустой\n");
break;
case(1):
printf("Можно дописывать информацию\n");
break;
case(2):
printf("Запись невозможна\n");
break;
default:
break;
}
return 0;
}
Если диск позволяет выполнить запись новой сессии (disk_status == 1), то алгоритм работы программы следующий:
- при помощи системного вызова stat определяется размер записываемого образа в блоках по 2048 байт (режим Data Mode 1):
#include <sys/stat.h>
struct stat s;
memset((void *)&s, 0, sizeof(struct stat));
/* argv[1] - имя файла-образа, передается в командной строке программы */
if(stat(argv[1], &s)) {
perror("stat");
exit(errno);
}
/* Размер файла-образа в блоках */
track_size = s.st_size/CD_FRAMESIZE;
- резервируется место для нового трека (команда RESERVE TRACK), при этом размер трека равен размеру файла-образа в блоках. Формат команды RESERVE TRACK был рассмотрен в [5]:
void reserv_track(__u32 track_size)
{
__u8 reserv_track_cmd[10];
__u32 size = 0;
memset(reserv_track_cmd, 0, 10);
reserv_track_cmd[0] = 0x53;
size = __swab32(track_size);
memcpy((void *)(reserv_track_cmd + 5), (void *)&size, 4);
send_cmd(reserv_track_cmd, 10, SG_DXFER_NONE, NULL, 0, 20);
return;
}
- при помощи команды READ TRACK INFORMATION определяется стартовый адрес зарезервированного трека, и начиная с этого адреса на диск выполняется запись данных.
Полный текст программы для записи многосессионных дисков находится по адресу: http://bob.netport.com.ua/iso2cd_multi.tar.gz.
Литература:
- ATAPI DVD Devices, ftp://ftp.seagate.com/sff/INF-8090.PDF
- SCSI MultiMedia Command Set, http://www.t10.org/ftp/t10/drafts/mmc5/mmc5r01.pdf.
- Мешков В. Пакетные команды интерфейса ATAPI. – Журнал «Системный администратор», № 9(22), сентябрь 2004 г. – 70-84 с.
- Мешков В. Запись дисков CD-R/RW в Linux. Часть 1. – Журнал «Системный администратор», № 11(24), ноябрь 2004 г. – 56-62 с.
- Мешков В. Запись дисков CD-R/RW в Linux. Часть 2. – Журнал «Системный администратор», № 12(25), декабрь 2004 г. – 68-75 с.