]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/cdrom_id/cdrom_id.c
Merge pull request #20303 from andir/sysconfig-example
[thirdparty/systemd.git] / src / udev / cdrom_id / cdrom_id.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3 * cdrom_id - optical drive and media information prober
4 */
5
6 #include <fcntl.h>
7 #include <getopt.h>
8 #include <linux/cdrom.h>
9 #include <scsi/sg.h>
10 #include <sys/ioctl.h>
11 #include <unistd.h>
12
13 #include "fd-util.h"
14 #include "main-func.h"
15 #include "memory-util.h"
16 #include "random-util.h"
17 #include "sort-util.h"
18 #include "string-table.h"
19 #include "string-util.h"
20 #include "udev-util.h"
21 #include "unaligned.h"
22
23 static bool arg_eject = false;
24 static bool arg_lock = false;
25 static bool arg_unlock = false;
26 static const char *arg_node = NULL;
27
28 typedef enum Feature {
29 FEATURE_RW_NONREMOVABLE = 0x01,
30 FEATURE_RW_REMOVABLE = 0x02,
31
32 FEATURE_MO_SE = 0x03, /* sector erase */
33 FEATURE_MO_WO = 0x04, /* write once */
34 FEATURE_MO_AS = 0x05, /* advance storage */
35
36 FEATURE_CD_ROM = 0x08,
37 FEATURE_CD_R = 0x09,
38 FEATURE_CD_RW = 0x0a,
39
40 FEATURE_DVD_ROM = 0x10,
41 FEATURE_DVD_R = 0x11,
42 FEATURE_DVD_RAM = 0x12,
43 FEATURE_DVD_RW_RO = 0x13, /* restricted overwrite mode */
44 FEATURE_DVD_RW_SEQ = 0x14, /* sequential mode */
45 FEATURE_DVD_R_DL_SEQ = 0x15, /* sequential recording */
46 FEATURE_DVD_R_DL_JR = 0x16, /* jump recording */
47 FEATURE_DVD_RW_DL = 0x17,
48 FEATURE_DVD_R_DDR = 0x18, /* download disc recording - dvd for css managed recording */
49 FEATURE_DVD_PLUS_RW = 0x1a,
50 FEATURE_DVD_PLUS_R = 0x1b,
51
52 FEATURE_DDCD_ROM = 0x20,
53 FEATURE_DDCD_R = 0x21,
54 FEATURE_DDCD_RW = 0x22,
55
56 FEATURE_DVD_PLUS_RW_DL = 0x2a,
57 FEATURE_DVD_PLUS_R_DL = 0x2b,
58
59 FEATURE_BD = 0x40,
60 FEATURE_BD_R_SRM = 0x41, /* sequential recording mode */
61 FEATURE_BD_R_RRM = 0x42, /* random recording mode */
62 FEATURE_BD_RE = 0x43,
63
64 FEATURE_HDDVD = 0x50,
65 FEATURE_HDDVD_R = 0x51,
66 FEATURE_HDDVD_RAM = 0x52,
67 FEATURE_HDDVD_RW = 0x53,
68 FEATURE_HDDVD_R_DL = 0x58,
69 FEATURE_HDDVD_RW_DL = 0x5a,
70
71 FEATURE_MRW,
72 FEATURE_MRW_W,
73
74 _FEATURE_MAX,
75 _FEATURE_INVALID = -EINVAL,
76 } Feature;
77
78 typedef enum MediaState {
79 MEDIA_STATE_BLANK = 0,
80 MEDIA_STATE_APPENDABLE = 1,
81 MEDIA_STATE_COMPLETE = 2,
82 MEDIA_STATE_OTHER = 3,
83 _MEDIA_STATE_MAX,
84 _MEDIA_STATE_INVALID = -EINVAL,
85 } MediaState;
86
87 typedef struct Context {
88 int fd;
89
90 Feature *drive_features;
91 size_t n_drive_feature;
92
93 Feature media_feature;
94 bool has_media;
95
96 MediaState media_state;
97 unsigned media_session_next;
98 unsigned media_session_count;
99 unsigned media_track_count;
100 unsigned media_track_count_data;
101 unsigned media_track_count_audio;
102 uint64_t media_session_last_offset;
103 } Context;
104
105 static void context_clear(Context *c) {
106 if (!c)
107 return;
108
109 safe_close(c->fd);
110 free(c->drive_features);
111 }
112
113 static void context_init(Context *c) {
114 assert(c);
115
116 *c = (Context) {
117 .fd = -1,
118 .media_feature = _FEATURE_INVALID,
119 .media_state = _MEDIA_STATE_INVALID,
120 };
121 }
122
123 static bool drive_has_feature(const Context *c, Feature f) {
124 assert(c);
125
126 for (size_t i = 0; i < c->n_drive_feature; i++)
127 if (c->drive_features[i] == f)
128 return true;
129
130 return false;
131 }
132
133 static int set_drive_feature(Context *c, Feature f) {
134 assert(c);
135
136 if (drive_has_feature(c, f))
137 return 0;
138
139 if (!GREEDY_REALLOC(c->drive_features, c->n_drive_feature + 1))
140 return -ENOMEM;
141
142 c->drive_features[c->n_drive_feature++] = f;
143 return 1;
144 }
145
146 #define ERRCODE(s) ((((s)[2] & 0x0F) << 16) | ((s)[12] << 8) | ((s)[13]))
147 #define SK(errcode) (((errcode) >> 16) & 0xF)
148 #define ASC(errcode) (((errcode) >> 8) & 0xFF)
149 #define ASCQ(errcode) ((errcode) & 0xFF)
150 #define CHECK_CONDITION 0x01
151
152 static int log_scsi_debug_errno(int error, const char *msg) {
153 assert(error != 0);
154
155 /* error < 0 means errno-style error, error > 0 means SCSI error */
156
157 if (error < 0)
158 return log_debug_errno(error, "Failed to %s: %m", msg);
159
160 return log_debug_errno(SYNTHETIC_ERRNO(EIO),
161 "Failed to %s with SK=%X/ASC=%02X/ACQ=%02X",
162 msg, SK(error), ASC(error), ASCQ(error));
163 }
164
165 struct scsi_cmd {
166 struct cdrom_generic_command cgc;
167 union {
168 struct request_sense s;
169 unsigned char u[18];
170 } _sense;
171 struct sg_io_hdr sg_io;
172 };
173
174 static void scsi_cmd_init(struct scsi_cmd *cmd) {
175 memzero(cmd, sizeof(struct scsi_cmd));
176 cmd->cgc.quiet = 1;
177 cmd->cgc.sense = &cmd->_sense.s;
178 cmd->sg_io.interface_id = 'S';
179 cmd->sg_io.mx_sb_len = sizeof(cmd->_sense);
180 cmd->sg_io.cmdp = cmd->cgc.cmd;
181 cmd->sg_io.sbp = cmd->_sense.u;
182 cmd->sg_io.flags = SG_FLAG_LUN_INHIBIT | SG_FLAG_DIRECT_IO;
183 }
184
185 static void scsi_cmd_set(struct scsi_cmd *cmd, size_t i, unsigned char arg) {
186 cmd->sg_io.cmd_len = i + 1;
187 cmd->cgc.cmd[i] = arg;
188 }
189
190 static int scsi_cmd_run(struct scsi_cmd *cmd, int fd, unsigned char *buf, size_t bufsize) {
191 int r;
192
193 assert(cmd);
194 assert(fd >= 0);
195 assert(buf || bufsize == 0);
196
197 /* Return 0 on success. On failure, return negative errno or positive error code. */
198
199 if (bufsize > 0) {
200 cmd->sg_io.dxferp = buf;
201 cmd->sg_io.dxfer_len = bufsize;
202 cmd->sg_io.dxfer_direction = SG_DXFER_FROM_DEV;
203 } else
204 cmd->sg_io.dxfer_direction = SG_DXFER_NONE;
205
206 if (ioctl(fd, SG_IO, &cmd->sg_io) < 0)
207 return -errno;
208
209 if ((cmd->sg_io.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
210 if (cmd->sg_io.masked_status & CHECK_CONDITION) {
211 r = ERRCODE(cmd->_sense.u);
212 if (r != 0)
213 return r;
214 }
215 return -EIO;
216 }
217
218 return 0;
219 }
220
221 static int scsi_cmd_run_and_log(struct scsi_cmd *cmd, int fd, unsigned char *buf, size_t bufsize, const char *msg) {
222 int r;
223
224 assert(msg);
225
226 r = scsi_cmd_run(cmd, fd, buf, bufsize);
227 if (r != 0)
228 return log_scsi_debug_errno(r, msg);
229
230 return 0;
231 }
232
233 static int media_lock(int fd, bool lock) {
234 /* disable the kernel's lock logic */
235 if (ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_LOCK) < 0)
236 log_debug_errno(errno, "Failed to issue ioctl(CDROM_CLEAR_OPTIONS, CDO_LOCK), ignoring: %m");
237
238 if (ioctl(fd, CDROM_LOCKDOOR, lock ? 1 : 0) < 0)
239 return log_debug_errno(errno, "Failed to issue ioctl(CDROM_LOCKDOOR): %m");
240
241 return 0;
242 }
243
244 static int media_eject(int fd) {
245 struct scsi_cmd sc;
246
247 scsi_cmd_init(&sc);
248 scsi_cmd_set(&sc, 0, GPCMD_START_STOP_UNIT);
249 scsi_cmd_set(&sc, 4, 0x02);
250 scsi_cmd_set(&sc, 5, 0);
251
252 return scsi_cmd_run_and_log(&sc, fd, NULL, 0, "start/stop unit");
253 }
254
255 static int cd_capability_compat(Context *c) {
256 int capability, r;
257
258 assert(c);
259
260 capability = ioctl(c->fd, CDROM_GET_CAPABILITY, NULL);
261 if (capability < 0)
262 return log_debug_errno(errno, "CDROM_GET_CAPABILITY failed");
263
264 if (capability & CDC_CD_R) {
265 r = set_drive_feature(c, FEATURE_CD_R);
266 if (r < 0)
267 return log_oom_debug();
268 }
269 if (capability & CDC_CD_RW) {
270 r = set_drive_feature(c, FEATURE_CD_RW);
271 if (r < 0)
272 return log_oom_debug();
273 }
274 if (capability & CDC_DVD) {
275 r = set_drive_feature(c, FEATURE_DVD_ROM);
276 if (r < 0)
277 return log_oom_debug();
278 }
279 if (capability & CDC_DVD_R) {
280 r = set_drive_feature(c, FEATURE_DVD_R);
281 if (r < 0)
282 return log_oom_debug();
283 }
284 if (capability & CDC_DVD_RAM) {
285 r = set_drive_feature(c, FEATURE_DVD_RAM);
286 if (r < 0)
287 return log_oom_debug();
288 }
289 if (capability & CDC_MRW) {
290 r = set_drive_feature(c, FEATURE_MRW);
291 if (r < 0)
292 return log_oom_debug();
293 }
294 if (capability & CDC_MRW_W) {
295 r = set_drive_feature(c, FEATURE_MRW_W);
296 if (r < 0)
297 return log_oom_debug();
298 }
299
300 return 0;
301 }
302
303 static int cd_media_compat(Context *c) {
304 int r;
305
306 assert(c);
307
308 r = ioctl(c->fd, CDROM_DRIVE_STATUS, CDSL_CURRENT);
309 if (r < 0)
310 return log_debug_errno(errno, "ioctl(CDROM_DRIVE_STATUS) failed: %m");
311 if (r != CDS_DISC_OK)
312 return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
313 "ioctl(CDROM_DRIVE_STATUS) → %d (%s), ignoring.",
314 r,
315 r == CDS_NO_INFO ? "no info" :
316 r == CDS_NO_DISC ? "no disc" :
317 r == CDS_TRAY_OPEN ? "tray open" :
318 r == CDS_DRIVE_NOT_READY ? "drive not ready" :
319 "unknown status");
320
321 c->has_media = true;
322 return 0;
323 }
324
325 static int cd_inquiry(Context *c) {
326 struct scsi_cmd sc;
327 unsigned char inq[36];
328 int r;
329
330 assert(c);
331
332 scsi_cmd_init(&sc);
333 scsi_cmd_set(&sc, 0, GPCMD_INQUIRY);
334 scsi_cmd_set(&sc, 4, sizeof(inq));
335 scsi_cmd_set(&sc, 5, 0);
336 r = scsi_cmd_run_and_log(&sc, c->fd, inq, sizeof(inq), "inquire");
337 if (r < 0)
338 return r;
339
340 if ((inq[0] & 0x1F) != 5)
341 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Not an MMC unit.");
342
343 log_debug("INQUIRY: [%.8s][%.16s][%.4s]", inq + 8, inq + 16, inq + 32);
344 return 0;
345 }
346
347 static int feature_profiles(Context *c, const unsigned char *profiles, size_t size) {
348 int r;
349
350 assert(c);
351
352 for (size_t i = 0; i + 4 <= size; i += 4) {
353 r = set_drive_feature(c, (Feature) unaligned_read_be16(&profiles[i]));
354 if (r < 0)
355 return log_oom_debug();
356 }
357
358 return 1;
359 }
360
361 static int cd_profiles_old_mmc(Context *c) {
362 disc_information discinfo;
363 struct scsi_cmd sc;
364 size_t len;
365 int r;
366
367 assert(c);
368
369 scsi_cmd_init(&sc);
370 scsi_cmd_set(&sc, 0, GPCMD_READ_DISC_INFO);
371 scsi_cmd_set(&sc, 8, sizeof(discinfo.disc_information_length));
372 scsi_cmd_set(&sc, 9, 0);
373 r = scsi_cmd_run_and_log(&sc, c->fd, (unsigned char *)&discinfo.disc_information_length, sizeof(discinfo.disc_information_length), "read disc information");
374 if (r >= 0) {
375 /* Not all drives have the same disc_info length, so requeue
376 * packet with the length the drive tells us it can supply */
377 len = be16toh(discinfo.disc_information_length) + sizeof(discinfo.disc_information_length);
378 if (len > sizeof(discinfo))
379 len = sizeof(discinfo);
380
381 scsi_cmd_init(&sc);
382 scsi_cmd_set(&sc, 0, GPCMD_READ_DISC_INFO);
383 scsi_cmd_set(&sc, 8, len);
384 scsi_cmd_set(&sc, 9, 0);
385 r = scsi_cmd_run_and_log(&sc, c->fd, (unsigned char *)&discinfo, len, "read disc information");
386 }
387 if (r < 0) {
388 if (c->has_media) {
389 log_debug("No current profile, but disc is present; assuming CD-ROM.");
390 c->media_feature = FEATURE_CD_ROM;
391 c->media_track_count = 1;
392 c->media_track_count_data = 1;
393 return 1;
394 } else
395 return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
396 "no current profile, assuming no media.");
397 };
398
399 c->has_media = true;
400
401 if (discinfo.erasable)
402 c->media_feature = FEATURE_CD_RW;
403 else if (discinfo.disc_status < 2 && drive_has_feature(c, FEATURE_CD_R))
404 c->media_feature = FEATURE_CD_R;
405 else
406 c->media_feature = FEATURE_CD_ROM;
407
408 return 0;
409 }
410
411 static int cd_profiles(Context *c) {
412 struct scsi_cmd sc;
413 unsigned char features[65530];
414 unsigned cur_profile;
415 size_t len;
416 int r;
417
418 assert(c);
419
420 /* First query the current profile */
421 scsi_cmd_init(&sc);
422 scsi_cmd_set(&sc, 0, GPCMD_GET_CONFIGURATION);
423 scsi_cmd_set(&sc, 8, 8);
424 scsi_cmd_set(&sc, 9, 0);
425 r = scsi_cmd_run(&sc, c->fd, features, 8);
426 if (r != 0) {
427 /* handle pre-MMC2 drives which do not support GET CONFIGURATION */
428 if (r > 0 && SK(r) == 0x5 && IN_SET(ASC(r), 0x20, 0x24)) {
429 log_debug("Drive is pre-MMC2 and does not support 46h get configuration command; "
430 "trying to work around the problem.");
431 return cd_profiles_old_mmc(c);
432 }
433
434 return log_scsi_debug_errno(r, "get configuration");
435 }
436
437 cur_profile = unaligned_read_be16(&features[6]);
438 if (cur_profile > 0) {
439 log_debug("current profile 0x%02x", cur_profile);
440 c->media_feature = (Feature) cur_profile;
441 c->has_media = true;
442 } else {
443 log_debug("no current profile, assuming no media");
444 c->has_media = false;
445 }
446
447 len = unaligned_read_be32(features);
448 log_debug("GET CONFIGURATION: size of features buffer %zu", len);
449
450 if (len > sizeof(features)) {
451 log_debug("Cannot get features in a single query, truncating.");
452 len = sizeof(features);
453 } else if (len <= 8)
454 len = sizeof(features);
455
456 /* Now get the full feature buffer */
457 scsi_cmd_init(&sc);
458 scsi_cmd_set(&sc, 0, GPCMD_GET_CONFIGURATION);
459 scsi_cmd_set(&sc, 7, (len >> 8) & 0xff);
460 scsi_cmd_set(&sc, 8, len & 0xff);
461 scsi_cmd_set(&sc, 9, 0);
462 r = scsi_cmd_run_and_log(&sc, c->fd, features, len, "get configuration");
463 if (r < 0)
464 return r;
465
466 /* parse the length once more, in case the drive decided to have other features suddenly :) */
467 len = unaligned_read_be32(features);
468 log_debug("GET CONFIGURATION: size of features buffer %zu", len);
469
470 if (len > sizeof(features)) {
471 log_debug("Cannot get features in a single query, truncating.");
472 len = sizeof(features);
473 }
474
475 /* device features */
476 for (size_t i = 8; i + 4 < len; i += 4 + features[i + 3]) {
477 unsigned feature;
478
479 feature = unaligned_read_be16(&features[i]);
480
481 switch (feature) {
482 case 0x00:
483 log_debug("GET CONFIGURATION: feature 'profiles', with %u entries", features[i + 3] / 4);
484 feature_profiles(c, features + i + 4, MIN(features[i + 3], len - i - 4));
485 break;
486 default:
487 log_debug("GET CONFIGURATION: feature 0x%04x <ignored>, with 0x%02x bytes", feature, features[i + 3]);
488 break;
489 }
490 }
491
492 return c->has_media;
493 }
494
495 static const char * const media_state_table[_MEDIA_STATE_MAX] = {
496 [MEDIA_STATE_BLANK] = "blank",
497 [MEDIA_STATE_APPENDABLE] = "appendable",
498 [MEDIA_STATE_COMPLETE] = "complete",
499 [MEDIA_STATE_OTHER] = "other",
500 };
501
502 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(media_state, MediaState);
503
504 static int dvd_ram_media_update_state(Context *c) {
505 struct scsi_cmd sc;
506 unsigned char dvdstruct[8];
507 unsigned char format[12];
508 unsigned char len;
509 int r;
510
511 assert(c);
512
513 /* Return 1 if media state is determined. */
514
515 if (c->media_feature != FEATURE_DVD_RAM)
516 return 0;
517
518 /* a write protected dvd-ram may report "complete" status */
519 scsi_cmd_init(&sc);
520 scsi_cmd_set(&sc, 0, GPCMD_READ_DVD_STRUCTURE);
521 scsi_cmd_set(&sc, 7, 0xC0);
522 scsi_cmd_set(&sc, 9, sizeof(dvdstruct));
523 scsi_cmd_set(&sc, 11, 0);
524 r = scsi_cmd_run_and_log(&sc, c->fd, dvdstruct, sizeof(dvdstruct), "read DVD structure");
525 if (r < 0)
526 return r;
527
528 if (dvdstruct[4] & 0x02) {
529 c->media_state = MEDIA_STATE_COMPLETE;
530 log_debug("Write-protected DVD-RAM media inserted");
531 return 1;
532 }
533
534 /* let's make sure we don't try to read unformatted media */
535 scsi_cmd_init(&sc);
536 scsi_cmd_set(&sc, 0, GPCMD_READ_FORMAT_CAPACITIES);
537 scsi_cmd_set(&sc, 8, sizeof(format));
538 scsi_cmd_set(&sc, 9, 0);
539 r = scsi_cmd_run_and_log(&sc, c->fd, format, sizeof(format), "read DVD format capacities");
540 if (r < 0)
541 return r;
542
543 len = format[3];
544 if (len & 7 || len < 16)
545 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
546 "Invalid format capacities length.");
547
548 switch(format[8] & 3) {
549 case 1:
550 /* This means that last format was interrupted or failed, blank dvd-ram discs are
551 * factory formatted. Take no action here as it takes quite a while to reformat a
552 * dvd-ram and it's not automatically started. */
553 log_debug("Unformatted DVD-RAM media inserted.");
554 return 1;
555
556 case 2:
557 log_debug("Formatted DVD-RAM media inserted.");
558 return 0;
559
560 case 3:
561 c->has_media = false;
562 return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
563 "Format capacities returned no media.");
564 }
565
566 return 0;
567 }
568
569 static int dvd_media_update_state(Context *c) {
570 struct scsi_cmd sc;
571 unsigned char buffer[32 * 2048];
572 int r;
573
574 r = dvd_ram_media_update_state(c);
575 if (r != 0)
576 return r;
577
578 /* Take a closer look at formatted media (unformatted DVD+RW
579 * has "blank" status", DVD-RAM was examined earlier) and check
580 * for ISO and UDF PVDs or a fs superblock presence and do it
581 * in one ioctl (we need just sectors 0 and 16) */
582
583 scsi_cmd_init(&sc);
584 scsi_cmd_set(&sc, 0, GPCMD_READ_10);
585 scsi_cmd_set(&sc, 5, 0);
586 scsi_cmd_set(&sc, 8, sizeof(buffer)/2048);
587 scsi_cmd_set(&sc, 9, 0);
588 r = scsi_cmd_run_and_log(&sc, c->fd, buffer, sizeof(buffer), "read first 32 blocks");
589 if (r < 0) {
590 c->has_media = false;
591 return r;
592 }
593
594 /* if any non-zero data is found in sector 16 (iso and udf) or
595 * eventually 0 (fat32 boot sector, ext2 superblock, etc), disc
596 * is assumed non-blank */
597
598 for (size_t offset = 32768; offset < 32768 + 2048; offset++)
599 if (buffer[offset] != 0) {
600 log_debug("Data in block 16, assuming complete.");
601 return 0;
602 }
603
604 for (size_t offset = 0; offset < 2048; offset++)
605 if (buffer[offset] != 0) {
606 log_debug("Data in block 0, assuming complete.");
607 return 0;
608 }
609
610 log_debug("No data in blocks 0 or 16, assuming blank.");
611 c->media_state = MEDIA_STATE_BLANK;
612 return 0;
613 }
614
615 static int cd_media_info(Context *c) {
616 struct scsi_cmd sc;
617 unsigned char header[32];
618 MediaState state;
619 int r;
620
621 assert(c);
622
623 scsi_cmd_init(&sc);
624 scsi_cmd_set(&sc, 0, GPCMD_READ_DISC_INFO);
625 scsi_cmd_set(&sc, 8, sizeof(header));
626 scsi_cmd_set(&sc, 9, 0);
627 r = scsi_cmd_run_and_log(&sc, c->fd, header, sizeof(header), "read disc information");
628 if (r < 0)
629 return r;
630
631 c->has_media = true;
632 log_debug("disk type %02x", header[8]);
633
634 state = (MediaState) (header[2] & 0x03);
635 log_debug("hardware reported media status: %s", strna(media_state_to_string(state)));
636
637 /* exclude plain CDROM, some fake cdroms return 0 for "blank" media here */
638 if (c->media_feature != FEATURE_CD_ROM)
639 c->media_state = state;
640
641 /* fresh DVD-RW in restricted overwrite mode reports itself as
642 * "appendable"; change it to "blank" to make it consistent with what
643 * gets reported after blanking, and what userspace expects. */
644 if (c->media_feature == FEATURE_DVD_RW_RO && state == MEDIA_STATE_APPENDABLE)
645 c->media_state = MEDIA_STATE_BLANK;
646
647 /* DVD+RW discs (and DVD-RW in restricted mode) once formatted are
648 * always "complete", DVD-RAM are "other" or "complete" if the disc is
649 * write protected; we need to check the contents if it is blank */
650 if (IN_SET(c->media_feature, FEATURE_DVD_RW_RO, FEATURE_DVD_PLUS_RW, FEATURE_DVD_PLUS_RW_DL, FEATURE_DVD_RAM) &&
651 IN_SET(state, MEDIA_STATE_COMPLETE, MEDIA_STATE_OTHER)) {
652 r = dvd_media_update_state(c);
653 if (r < 0)
654 return r;
655 }
656
657 /* "other" is e. g. DVD-RAM, can't append sessions there; DVDs in
658 * restricted overwrite mode can never append, only in sequential mode */
659 if (c->media_feature != FEATURE_DVD_RW_RO && IN_SET(state, MEDIA_STATE_BLANK, MEDIA_STATE_APPENDABLE))
660 c->media_session_next = header[10] << 8 | header[5];
661 c->media_session_count = header[9] << 8 | header[4];
662 c->media_track_count = header[11] << 8 | header[6];
663
664 return 0;
665 }
666
667 static int cd_media_toc(Context *c) {
668 struct scsi_cmd sc;
669 unsigned char header[12];
670 unsigned char toc[65536];
671 unsigned num_tracks;
672 size_t len;
673 int r;
674
675 assert(c);
676
677 scsi_cmd_init(&sc);
678 scsi_cmd_set(&sc, 0, GPCMD_READ_TOC_PMA_ATIP);
679 scsi_cmd_set(&sc, 6, 1);
680 scsi_cmd_set(&sc, 8, sizeof(header));
681 scsi_cmd_set(&sc, 9, 0);
682 r = scsi_cmd_run_and_log(&sc, c->fd, header, sizeof(header), "read TOC");
683 if (r < 0)
684 return r;
685
686 len = unaligned_read_be16(header) + 2;
687 log_debug("READ TOC: len: %zu, start track: %u, end track: %u", len, header[2], header[3]);
688
689 if (len > sizeof(toc))
690 return -1;
691 /* empty media has no tracks */
692 if (len < 8)
693 return 0;
694
695 /* 2: first track, 3: last track */
696 num_tracks = header[3] - header[2] + 1;
697
698 scsi_cmd_init(&sc);
699 scsi_cmd_set(&sc, 0, GPCMD_READ_TOC_PMA_ATIP);
700 scsi_cmd_set(&sc, 6, header[2]); /* First Track/Session Number */
701 scsi_cmd_set(&sc, 7, (len >> 8) & 0xff);
702 scsi_cmd_set(&sc, 8, len & 0xff);
703 scsi_cmd_set(&sc, 9, 0);
704 r = scsi_cmd_run_and_log(&sc, c->fd, toc, len, "read TOC (tracks)");
705 if (r < 0)
706 return r;
707
708 /* Take care to not iterate beyond the last valid track as specified in
709 * the TOC, but also avoid going beyond the TOC length, just in case
710 * the last track number is invalidly large */
711 for (size_t i = 4; i + 8 < len && num_tracks > 0; i += 8, --num_tracks) {
712 bool is_data_track;
713 uint32_t block;
714
715 is_data_track = (toc[i + 1] & 0x04) != 0;
716 block = unaligned_read_be32(&toc[i + 4]);
717
718 log_debug("track=%u info=0x%x(%s) start_block=%"PRIu32,
719 toc[i + 2], toc[i + 1] & 0x0f, is_data_track ? "data":"audio", block);
720
721 if (is_data_track)
722 c->media_track_count_data++;
723 else
724 c->media_track_count_audio++;
725 }
726
727 scsi_cmd_init(&sc);
728 scsi_cmd_set(&sc, 0, GPCMD_READ_TOC_PMA_ATIP);
729 scsi_cmd_set(&sc, 2, 1); /* Session Info */
730 scsi_cmd_set(&sc, 8, sizeof(header));
731 scsi_cmd_set(&sc, 9, 0);
732 r = scsi_cmd_run_and_log(&sc, c->fd, header, sizeof(header), "read TOC (multi session)");
733 if (r < 0)
734 return r;
735
736 len = unaligned_read_be32(&header[8]);
737 log_debug("last track %u starts at block %zu", header[4+2], len);
738 c->media_session_last_offset = (uint64_t) len * 2048;
739
740 return 0;
741 }
742
743 static int open_drive(Context *c) {
744 int fd;
745
746 assert(c);
747 assert(c->fd < 0);
748
749 for (int cnt = 0;; cnt++) {
750 fd = open(arg_node, O_RDONLY|O_NONBLOCK|O_CLOEXEC);
751 if (fd >= 0)
752 break;
753 if (++cnt >= 20 || errno != EBUSY)
754 return log_debug_errno(errno, "Unable to open '%s': %m", arg_node);
755
756 (void) usleep(100 * USEC_PER_MSEC + random_u64_range(100 * USEC_PER_MSEC));
757 }
758
759 log_debug("probing: '%s'", arg_node);
760 c->fd = fd;
761 return 0;
762 }
763
764 typedef struct FeatureToString {
765 Feature feature;
766 const char *str;
767 } FeatureToString;
768
769 static const FeatureToString feature_to_string[] = {
770 { .feature = FEATURE_RW_NONREMOVABLE, .str = "RW_NONREMOVABLE", },
771 { .feature = FEATURE_RW_REMOVABLE, .str = "RW_REMOVABLE", },
772
773 { .feature = FEATURE_MO_SE, .str = "MO_SE", },
774 { .feature = FEATURE_MO_WO, .str = "MO_WO", },
775 { .feature = FEATURE_MO_AS, .str = "MO_AS", },
776
777 { .feature = FEATURE_CD_ROM, .str = "CD", },
778 { .feature = FEATURE_CD_R, .str = "CD_R", },
779 { .feature = FEATURE_CD_RW, .str = "CD_RW", },
780
781 { .feature = FEATURE_DVD_ROM, .str = "DVD", },
782 { .feature = FEATURE_DVD_R, .str = "DVD_R", },
783 { .feature = FEATURE_DVD_RAM, .str = "DVD_RAM", },
784 { .feature = FEATURE_DVD_RW_RO, .str = "DVD_RW_RO", },
785 { .feature = FEATURE_DVD_RW_SEQ, .str = "DVD_RW_SEQ", },
786 { .feature = FEATURE_DVD_R_DL_SEQ, .str = "DVD_R_DL_SEQ", },
787 { .feature = FEATURE_DVD_R_DL_JR, .str = "DVD_R_DL_JR", },
788 { .feature = FEATURE_DVD_RW_DL, .str = "DVD_RW_DL", },
789 { .feature = FEATURE_DVD_R_DDR, .str = "DVD_R_DDR", },
790 { .feature = FEATURE_DVD_PLUS_RW, .str = "DVD_PLUS_RW", },
791 { .feature = FEATURE_DVD_PLUS_R, .str = "DVD_PLUS_R", },
792
793 { .feature = FEATURE_DDCD_ROM, .str = "DDCD", },
794 { .feature = FEATURE_DDCD_R, .str = "DDCD_R", },
795 { .feature = FEATURE_DDCD_RW, .str = "DDCD_RW", },
796
797 { .feature = FEATURE_DVD_PLUS_RW_DL, .str = "DVD_PLUS_RW_DL", },
798 { .feature = FEATURE_DVD_PLUS_R_DL, .str = "DVD_PLUS_R_DL", },
799
800 { .feature = FEATURE_BD, .str = "BD", },
801 { .feature = FEATURE_BD_R_SRM, .str = "BD_R_SRM", },
802 { .feature = FEATURE_BD_R_RRM, .str = "BD_R_RRM", },
803 { .feature = FEATURE_BD_RE, .str = "BD_RE", },
804
805 { .feature = FEATURE_HDDVD, .str = "HDDVD", },
806 { .feature = FEATURE_HDDVD_R, .str = "HDDVD_R", },
807 { .feature = FEATURE_HDDVD_RAM, .str = "HDDVD_RAM", },
808 { .feature = FEATURE_HDDVD_RW, .str = "HDDVD_RW", },
809 { .feature = FEATURE_HDDVD_R_DL, .str = "HDDVD_R_DL", },
810 { .feature = FEATURE_HDDVD_RW_DL, .str = "HDDVD_RW_DL", },
811
812 { .feature = FEATURE_MRW, .str = "MRW", },
813 { .feature = FEATURE_MRW_W, .str = "MRW_W", },
814 };
815
816 static int feature_to_string_compare_func(const FeatureToString *a, const FeatureToString *b) {
817 assert(a);
818 assert(b);
819
820 return CMP(a->feature, b->feature);
821 }
822
823 static void print_feature(Feature feature, const char *prefix) {
824 FeatureToString *found, in = {
825 .feature = feature,
826 };
827
828 assert(prefix);
829
830 found = typesafe_bsearch(&in, feature_to_string, ELEMENTSOF(feature_to_string), feature_to_string_compare_func);
831 if (!found)
832 return (void) log_debug("Unknown feature 0x%02x, ignoring.", (unsigned) feature);
833
834 printf("%s_%s=1\n", prefix, found->str);
835 }
836
837 static void print_properties(const Context *c) {
838 const char *state;
839
840 assert(c);
841
842 printf("ID_CDROM=1\n");
843 for (size_t i = 0; i < c->n_drive_feature; i++)
844 print_feature(c->drive_features[i], "ID_CDROM");
845
846 if (drive_has_feature(c, FEATURE_MO_SE) ||
847 drive_has_feature(c, FEATURE_MO_WO) ||
848 drive_has_feature(c, FEATURE_MO_AS))
849 printf("ID_CDROM_MO=1\n");
850
851 if (drive_has_feature(c, FEATURE_DVD_RW_RO) ||
852 drive_has_feature(c, FEATURE_DVD_RW_SEQ))
853 printf("ID_CDROM_DVD_RW=1\n");
854
855 if (drive_has_feature(c, FEATURE_DVD_R_DL_SEQ) ||
856 drive_has_feature(c, FEATURE_DVD_R_DL_JR))
857 printf("ID_CDROM_DVD_R_DL=1\n");
858
859 if (drive_has_feature(c, FEATURE_DVD_R_DDR))
860 printf("ID_CDROM_DVD_R=1\n");
861
862 if (drive_has_feature(c, FEATURE_BD_R_SRM) ||
863 drive_has_feature(c, FEATURE_BD_R_RRM))
864 printf("ID_CDROM_BD_R=1\n");
865
866 if (c->has_media) {
867 printf("ID_CDROM_MEDIA=1\n");
868 print_feature(c->media_feature, "ID_CDROM_MEDIA");
869
870 if (IN_SET(c->media_feature, FEATURE_MO_SE, FEATURE_MO_WO, FEATURE_MO_AS))
871 printf("ID_CDROM_MEDIA_MO=1\n");
872
873 if (IN_SET(c->media_feature, FEATURE_DVD_RW_RO, FEATURE_DVD_RW_SEQ))
874 printf("ID_CDROM_MEDIA_DVD_RW=1\n");
875
876 if (IN_SET(c->media_feature, FEATURE_DVD_R_DL_SEQ, FEATURE_DVD_R_DL_JR))
877 printf("ID_CDROM_MEDIA_DVD_R_DL=1\n");
878
879 if (c->media_feature == FEATURE_DVD_R_DDR)
880 printf("ID_CDROM_MEDIA_DVD_R=1\n");
881
882 if (IN_SET(c->media_feature, FEATURE_BD_R_SRM, FEATURE_BD_R_RRM))
883 printf("ID_CDROM_MEDIA_BD_R=1\n");
884 }
885
886 state = media_state_to_string(c->media_state);
887 if (state)
888 printf("ID_CDROM_MEDIA_STATE=%s\n", state);
889 if (c->media_session_next > 0)
890 printf("ID_CDROM_MEDIA_SESSION_NEXT=%u\n", c->media_session_next);
891 if (c->media_session_count > 0)
892 printf("ID_CDROM_MEDIA_SESSION_COUNT=%u\n", c->media_session_count);
893 if (c->media_session_count > 1 && c->media_session_last_offset > 0)
894 printf("ID_CDROM_MEDIA_SESSION_LAST_OFFSET=%" PRIu64 "\n", c->media_session_last_offset);
895 if (c->media_track_count > 0)
896 printf("ID_CDROM_MEDIA_TRACK_COUNT=%u\n", c->media_track_count);
897 if (c->media_track_count_audio > 0)
898 printf("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO=%u\n", c->media_track_count_audio);
899 if (c->media_track_count_data > 0)
900 printf("ID_CDROM_MEDIA_TRACK_COUNT_DATA=%u\n", c->media_track_count_data);
901 }
902
903 static int help(void) {
904 printf("Usage: %s [options] <device>\n"
905 " -l --lock-media lock the media (to enable eject request events)\n"
906 " -u --unlock-media unlock the media\n"
907 " -e --eject-media eject the media\n"
908 " -d --debug print debug messages to stderr\n"
909 " -h --help print this help text\n"
910 "\n",
911 program_invocation_short_name);
912
913 return 0;
914 }
915
916 static int parse_argv(int argc, char *argv[]) {
917 static const struct option options[] = {
918 { "lock-media", no_argument, NULL, 'l' },
919 { "unlock-media", no_argument, NULL, 'u' },
920 { "eject-media", no_argument, NULL, 'e' },
921 { "debug", no_argument, NULL, 'd' },
922 { "help", no_argument, NULL, 'h' },
923 {}
924 };
925 int c;
926
927 while ((c = getopt_long(argc, argv, "deluh", options, NULL)) >= 0)
928 switch (c) {
929 case 'l':
930 arg_lock = true;
931 break;
932 case 'u':
933 arg_unlock = true;
934 break;
935 case 'e':
936 arg_eject = true;
937 break;
938 case 'd':
939 log_set_target(LOG_TARGET_CONSOLE);
940 log_set_max_level(LOG_DEBUG);
941 log_open();
942 break;
943 case 'h':
944 return help();
945 default:
946 assert_not_reached();
947 }
948
949 arg_node = argv[optind];
950 if (!arg_node)
951 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No device specified.");
952
953 return 1;
954 }
955
956 static int run(int argc, char *argv[]) {
957 _cleanup_(context_clear) Context c;
958 int r;
959
960 log_set_target(LOG_TARGET_AUTO);
961 udev_parse_config();
962 log_parse_environment();
963 log_open();
964
965 context_init(&c);
966
967 r = parse_argv(argc, argv);
968 if (r <= 0)
969 return r;
970
971 r = open_drive(&c);
972 if (r < 0)
973 return r;
974
975 /* same data as original cdrom_id */
976 r = cd_capability_compat(&c);
977 if (r < 0)
978 return r;
979
980 /* check for media - don't bail if there's no media as we still need to
981 * to read profiles */
982 (void) cd_media_compat(&c);
983
984 /* check if drive talks MMC */
985 if (cd_inquiry(&c) < 0)
986 goto work;
987
988 r = cd_profiles(&c); /* read drive and possibly current profile */
989 if (r > 0) {
990 /* at this point we are guaranteed to have media in the drive - find out more about it */
991
992 /* get session/track info */
993 (void) cd_media_toc(&c);
994
995 /* get writable media state */
996 (void) cd_media_info(&c);
997 }
998
999 work:
1000 /* lock the media, so we enable eject button events */
1001 if (arg_lock && c.has_media) {
1002 log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (lock)");
1003 (void) media_lock(c.fd, true);
1004 }
1005
1006 if (arg_unlock && c.has_media) {
1007 log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)");
1008 (void) media_lock(c.fd, false);
1009 }
1010
1011 if (arg_eject) {
1012 log_debug("PREVENT_ALLOW_MEDIUM_REMOVAL (unlock)");
1013 (void) media_lock(c.fd, false);
1014 log_debug("START_STOP_UNIT (eject)");
1015 (void) media_eject(c.fd);
1016 }
1017
1018 print_properties(&c);
1019
1020 return 0;
1021 }
1022
1023 DEFINE_MAIN_FUNCTION(run);