1 /***************************************************************************
   2  *
   3  * cdutils.c : CD/DVD utilities
   4  *
   5  * Copyright 2006, 2015, Oracle and/or its affiliates. All rights reserved.
   6  * Use is subject to license terms.
   7  *
   8  * Licensed under the Academic Free License version 2.1
   9  *
  10  **************************************************************************/
  11 
  12 
  13 #ifdef HAVE_CONFIG_H
  14 #  include <config.h>
  15 #endif
  16 
  17 #include <stdio.h>
  18 #include <sys/types.h>
  19 #include <sys/scsi/impl/uscsi.h>
  20 #include <string.h>
  21 #include <strings.h>
  22 #include <unistd.h>
  23 #include <stdlib.h>
  24 #include <errno.h>
  25 #include <fcntl.h>
  26 #include <sys/dkio.h>
  27 #include <libintl.h>
  28 
  29 #include <logger.h>
  30 
  31 #include "cdutils.h"
  32 
  33 #define RQLEN   32
  34 #define SENSE_KEY(rqbuf)        (rqbuf[2])      /* scsi error category */
  35 #define ASC(rqbuf)              (rqbuf[12])     /* additional sense code */
  36 #define ASCQ(rqbuf)             (rqbuf[13])     /* ASC qualifier */
  37 
  38 #define GET16(a) (((a)[0] << 8) | (a)[1])
  39 #define GET32(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
  40 
  41 #define CD_USCSI_TIMEOUT        60
  42 
  43 void
  44 uscsi_cmd_init(struct uscsi_cmd *scmd, char *cdb, int cdblen)
  45 {
  46         bzero(scmd, sizeof (*scmd));
  47         bzero(cdb, cdblen);
  48         scmd->uscsi_cdb = cdb;
  49 }
  50 
  51 int
  52 uscsi(int fd, struct uscsi_cmd *scmd)
  53 {
  54         char            rqbuf[RQLEN];
  55         int             ret;
  56         int             i, retries, total_retries;
  57         int             max_retries = 20;
  58 
  59         scmd->uscsi_flags |= USCSI_RQENABLE;
  60         scmd->uscsi_rqlen = RQLEN;
  61         scmd->uscsi_rqbuf = rqbuf;
  62 
  63         for (retries = 0; retries < max_retries; retries++) {
  64                 scmd->uscsi_status = 0;
  65                 memset(rqbuf, 0, RQLEN);
  66 
  67                 ret = ioctl(fd, USCSICMD, scmd);
  68 
  69                 if ((ret == 0) && (scmd->uscsi_status == 2)) {
  70                         ret = -1;
  71                         errno = EIO;
  72                 }
  73                 if ((ret < 0) && (scmd->uscsi_status == 2)) {
  74                         /*
  75                          * The drive is not ready to recieve commands but
  76                          * may be in the process of becoming ready.
  77                          * sleep for a short time then retry command.
  78                          * SENSE/ASC = 2/4 : not ready
  79                          * ASCQ = 0  Not Reportable.
  80                          * ASCQ = 1  Becoming ready.
  81                          * ASCQ = 4  FORMAT in progress.
  82                          * ASCQ = 7  Operation in progress.
  83                          */
  84                         if ((SENSE_KEY(rqbuf) == 2) && (ASC(rqbuf) == 4) &&
  85                             ((ASCQ(rqbuf) == 0) || (ASCQ(rqbuf) == 1) ||
  86                             (ASCQ(rqbuf) == 4)) || (ASCQ(rqbuf) == 7)) {
  87                                 total_retries++;
  88                                 sleep(1);
  89                                 continue;
  90                         }
  91 
  92                         /*
  93                          * Device is not ready to transmit or a device reset
  94                          * has occurred. wait for a short period of time then
  95                          * retry the command.
  96                          */
  97                         if ((SENSE_KEY(rqbuf) == 6) && ((ASC(rqbuf) == 0x28) ||
  98                             (ASC(rqbuf) == 0x29))) {
  99                                 sleep(1);
 100                                 total_retries++;
 101                                 continue;
 102                         }
 103                         /*
 104                          * Blank Sense, we don't know what the error is or if
 105                          * the command succeeded, Hope for the best. Some
 106                          * drives return blank sense periodically and will
 107                          * fail if this is removed.
 108                          */
 109                         if ((SENSE_KEY(rqbuf) == 0) && (ASC(rqbuf) == 0) &&
 110                             (ASCQ(rqbuf) == 0)) {
 111                                 ret = 0;
 112                                 break;
 113                         }
 114 
 115                         HAL_DEBUG (("cmd: 0x%02x ret:%i status:%02x "
 116                             " sense: %02x ASC: %02x ASCQ:%02x\n",
 117                             (uchar_t)scmd->uscsi_cdb[0], ret,
 118                             scmd->uscsi_status,
 119                             (uchar_t)SENSE_KEY(rqbuf),
 120                             (uchar_t)ASC(rqbuf), (uchar_t)ASCQ(rqbuf)));
 121                 }
 122 
 123                 break;
 124         }
 125 
 126         if (retries) {
 127                 HAL_DEBUG (("total retries: %d\n", total_retries));
 128         }
 129 
 130         return (ret);
 131 }
 132 
 133 int
 134 mode_sense(int fd, uchar_t pc, int dbd, int page_len, uchar_t *buffer)
 135 {
 136         struct uscsi_cmd scmd;
 137         char cdb[16];
 138 
 139         uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
 140         scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
 141         scmd.uscsi_buflen = page_len;
 142         scmd.uscsi_bufaddr = (char *)buffer;
 143         scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
 144         scmd.uscsi_cdblen = 0xa;
 145         scmd.uscsi_cdb[0] = 0x5a; /* MODE SENSE 10 */
 146         if (dbd) {
 147                 scmd.uscsi_cdb[1] = 0x8; /* no block descriptors */
 148         }
 149         scmd.uscsi_cdb[2] = pc;
 150         scmd.uscsi_cdb[7] = (page_len >> 8) & 0xff;
 151         scmd.uscsi_cdb[8] = page_len & 0xff;
 152 
 153         return (uscsi(fd, &scmd) == 0);
 154 }
 155 
 156 /*
 157  * will get the mode page only i.e. will strip off the header.
 158  */
 159 int
 160 get_mode_page(int fd, int page_no, int pc, int buf_len, uchar_t *buffer, int *plen)
 161 {
 162         int ret;
 163         uchar_t byte2;
 164         uchar_t buf[256];
 165         uint_t header_len, page_len, copy_cnt;
 166 
 167         byte2 = (uchar_t)(((pc << 6) & 0xC0) | (page_no & 0x3f));
 168 
 169         /* Ask 254 bytes only to make our IDE driver happy */
 170         if ((ret = mode_sense(fd, byte2, 1, 254, buf)) == 0) {
 171                 return (0);
 172         }
 173 
 174         header_len = 8 + GET16(&buf[6]);
 175         page_len = buf[header_len + 1] + 2;
 176 
 177         copy_cnt = (page_len > buf_len) ? buf_len : page_len;
 178         (void) memcpy(buffer, &buf[header_len], copy_cnt);
 179 
 180         if (plen) {
 181                 *plen = page_len;
 182         }
 183 
 184         return (1);
 185 }
 186 
 187 /* Get information about the Logical Unit's capabilities */
 188 int
 189 get_configuration(int fd, uint16_t feature, int bufsize, uchar_t *buf)
 190 {
 191         struct uscsi_cmd scmd;
 192         char cdb[16];
 193 
 194         uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
 195         scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
 196         scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
 197         scmd.uscsi_cdb[0] = 0x46; /* GET CONFIGURATION */
 198         scmd.uscsi_cdb[1] = 0x2; /* request type */
 199         scmd.uscsi_cdb[2] = (feature >> 8) & 0xff; /* starting feature # */
 200         scmd.uscsi_cdb[3] = feature & 0xff;
 201         scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
 202         scmd.uscsi_cdb[8] = bufsize & 0xff;
 203         scmd.uscsi_cdblen = 10;
 204         scmd.uscsi_bufaddr = (char *)buf;
 205         scmd.uscsi_buflen = bufsize;
 206 
 207         return (uscsi(fd, &scmd) == 0);
 208 }
 209 
 210 boolean_t
 211 get_current_profile(int fd, int *profile)
 212 {
 213         size_t i;
 214         uchar_t smallbuf[8];
 215         size_t buflen;
 216         uchar_t *bufp;
 217         int ret = B_FALSE;
 218 
 219         /*
 220          * first determine amount of memory needed to hold all profiles.
 221          * The first four bytes of smallbuf concatenated tell us the
 222          * number of bytes of memory we need but do not take themselves
 223          * into account. Therefore, add four to allocate that number
 224          * of bytes.
 225          */
 226         if (get_configuration(fd, 0, 8, &smallbuf[0])) {
 227                 buflen = GET32(smallbuf) + 4;
 228                 bufp = (uchar_t *)malloc(buflen);
 229 
 230                 /* now get all profiles */
 231                 if (get_configuration(fd, 0, buflen, bufp)) {
 232                         *profile = GET16(&bufp[6]);
 233                         ret = B_TRUE;
 234                 }
 235                 free(bufp);
 236         }
 237 
 238         return (ret);
 239 }
 240 
 241 void
 242 walk_profiles(int fd, int (*f)(void *, int, boolean_t), void *arg)
 243 {
 244         size_t i;
 245         uint16_t profile, current_profile;
 246         uchar_t smallbuf[8];
 247         size_t buflen;
 248         uchar_t *bufp;
 249         int ret;
 250 
 251         /*
 252          * first determine amount of memory needed to hold all profiles.
 253          * The first four bytes of smallbuf concatenated tell us the
 254          * number of bytes of memory we need but do not take themselves
 255          * into account. Therefore, add four to allocate that number
 256          * of bytes.
 257          */
 258         if (get_configuration(fd, 0, 8, &smallbuf[0])) {
 259                 buflen = GET32(smallbuf) + 4;
 260                 bufp = (uchar_t *)malloc(buflen);
 261 
 262                 /* now get all profiles */
 263                 if (get_configuration(fd, 0, buflen, bufp)) {
 264                         current_profile = GET16(&bufp[6]);
 265                         for (i = 8 + 4;  i < buflen; i += 4) {
 266                                 profile = GET16(&bufp[i]);
 267                                 ret = f(arg, profile, (profile == current_profile));
 268                                 if (ret == CDUTIL_WALK_STOP) {
 269                                         break;
 270                                 }
 271                         }
 272                 }
 273 
 274                 free(bufp);
 275         }
 276 }
 277 
 278 /* retrieve speed list from the Write Speed Performance Descriptor Blocks
 279  */
 280 void
 281 get_write_speeds(uchar_t *page, int n, intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
 282 {
 283         uchar_t *p = page + 2;
 284         int     i;
 285         intlist_t **nextp;
 286         intlist_t *current;
 287         boolean_t skip;
 288 
 289         *n_speeds = 0;
 290         *speeds = NULL;
 291         *speeds_mem = (intlist_t *)calloc(n, sizeof (intlist_t));
 292         if (*speeds_mem == NULL) {
 293                 return;
 294         }
 295 
 296         for (i = 0; i < n; i++, p += 4) {
 297                 current = &(*speeds_mem)[i];
 298                 current->val = GET16(p);
 299 
 300                 /* keep the list sorted */
 301                 skip = B_FALSE;
 302                 for (nextp = speeds; *nextp != NULL; nextp = &((*nextp)->next)) {
 303                         if (current->val == (*nextp)->val) {
 304                                 skip = B_TRUE; /* skip duplicates */
 305                                 break;
 306                         } else if (current->val > (*nextp)->val) {
 307                                 break;
 308                         }
 309                 }
 310                 if (!skip) {
 311                         current->next = *nextp;
 312                         *nextp = current;
 313                         (*n_speeds)++;
 314                 }
 315         }
 316 }
 317 
 318 void
 319 get_read_write_speeds(int fd, int *read_speed, int *write_speed,
 320     intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
 321 {
 322         int page_len;
 323         uchar_t p[254];
 324         int n; /* number of write speed performance descriptor blocks */
 325 
 326         *read_speed = *write_speed = 0;
 327         *speeds = *speeds_mem = NULL;
 328 
 329         if (!get_mode_page(fd, 0x2A, 0, sizeof (p), p, &page_len)) {
 330                 return;
 331         }
 332 
 333         if (page_len > 8) {
 334                 *read_speed = GET16(&p[8]);
 335         }
 336         if (page_len > 18) {
 337                 *write_speed = GET16(&p[18]);
 338         }
 339         if (page_len < 28) {
 340                 printf("MMC-2\n");
 341                 return;
 342         } else {
 343                 printf("MMC-3\n");
 344         }
 345 
 346         *write_speed = GET16(&p[28]);
 347 
 348         if (page_len < 30) {
 349                 return;
 350         }
 351 
 352         /* retrieve speed list */
 353         n = GET16(&p[30]);
 354         n = min(n, (sizeof (p) - 32) / 4);
 355 
 356         get_write_speeds(&p[32], n, speeds, n_speeds, speeds_mem);
 357 
 358         if (*speeds != NULL) {
 359                 *write_speed = max(*write_speed, (*speeds)[0].val);
 360         }
 361 }
 362 
 363 boolean_t
 364 get_disc_info(int fd, disc_info_t *di)
 365 {
 366         struct uscsi_cmd scmd;
 367         char cdb[16];
 368         uint8_t buf[32];
 369         int bufsize = sizeof (buf);
 370 
 371         bzero(buf, bufsize);
 372         uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
 373         scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
 374         scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
 375         scmd.uscsi_cdb[0] = 0x51; /* READ DISC INFORMATION */
 376         scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
 377         scmd.uscsi_cdb[8] = bufsize & 0xff;
 378         scmd.uscsi_cdblen = 10;
 379         scmd.uscsi_bufaddr = (char *)buf;
 380         scmd.uscsi_buflen = bufsize;
 381 
 382         if ((uscsi(fd, &scmd)) != 0) {
 383                 return (B_FALSE);
 384         }
 385 
 386         /*
 387          * According to MMC-5 6.22.3.2, the Disc Information Length should be
 388          * 32+8*(Number of OPC Tables). Some devices, like U3 sticks, return 0.
 389          * Yet some drives can return less than 32. We only need the first 22.
 390          */
 391         if (GET16(&buf[0]) < 22) {
 392                 return (B_FALSE);
 393         }
 394 
 395         di->disc_status = buf[2] & 0x03;
 396         di->erasable = buf[2] & 0x10;
 397         if ((buf[21] != 0) && (buf[21] != 0xff)) {
 398                 di->capacity = ((buf[21] * 60) + buf[22]) * 75;
 399         } else {
 400                 di->capacity = 0;
 401         }
 402 
 403         return (B_TRUE);
 404 }
 405 
 406 /*
 407  * returns current/maximum format capacity in bytes
 408  */
 409 boolean_t
 410 read_format_capacity(int fd, uint64_t *capacity)
 411 {
 412         struct uscsi_cmd scmd;
 413         char cdb[16];
 414         uint8_t buf[32];
 415         int bufsize = sizeof (buf);
 416         uint32_t num_blocks;
 417         uint32_t block_len;
 418 
 419         bzero(buf, bufsize);
 420         uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
 421         scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
 422         scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
 423         scmd.uscsi_cdb[0] = 0x23; /* READ FORMAT CAPACITIRES */
 424         scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
 425         scmd.uscsi_cdb[8] = bufsize & 0xff;
 426         scmd.uscsi_cdblen = 12;
 427         scmd.uscsi_bufaddr = (char *)buf;
 428         scmd.uscsi_buflen = bufsize;
 429 
 430         if ((uscsi(fd, &scmd)) != 0) {
 431                 return (B_FALSE);
 432         }
 433 
 434         num_blocks = (uint32_t)(buf[4] << 24) + (buf[5] << 16) + (buf[6] << 8) + buf[7];
 435         block_len = (uint32_t)(buf[9] << 16) + (buf[10] << 8) + buf[11];
 436         *capacity = (uint64_t)num_blocks * block_len;
 437 
 438         return (B_TRUE);
 439 }
 440 
 441 boolean_t
 442 get_media_info(int fd, struct dk_minfo *minfop)
 443 {
 444         return (ioctl(fd, DKIOCGMEDIAINFO, minfop) != -1);
 445 }
 446 
 447 /*
 448  * given current profile, use the best method for determining
 449  * disc capacity (in bytes)
 450  */
 451 boolean_t
 452 get_disc_capacity_for_profile(int fd, int profile, uint64_t *capacity)
 453 {
 454         struct dk_minfo mi;
 455         disc_info_t     di;
 456         boolean_t       ret = B_FALSE;
 457 
 458         switch (profile) {
 459         case 0x08: /* CD-ROM */
 460         case 0x10: /* DVD-ROM */
 461                 if (get_media_info(fd, &mi) && (mi.dki_capacity > 1)) {
 462                         *capacity = mi.dki_capacity * mi.dki_lbsize;
 463                         ret = B_TRUE;
 464                 }
 465                 break;
 466         default:
 467                 if (read_format_capacity(fd, capacity) && (*capacity > 0)) {
 468                         ret = B_TRUE;
 469                 } else if (get_disc_info(fd, &di) && (di.capacity > 0)) {
 470                         if (get_media_info(fd, &mi)) {
 471                                 *capacity = di.capacity * mi.dki_lbsize;
 472                                 ret = B_TRUE;
 473                         }
 474                 }
 475         }
 476 
 477         return (ret);
 478 }
 479 
 480 boolean_t
 481 read_toc(int fd, int format, int trackno, int buflen, uchar_t *buf)
 482 {
 483         struct uscsi_cmd scmd;
 484         char cdb[16];
 485 
 486         bzero(buf, buflen);
 487         uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
 488         scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
 489         scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
 490         scmd.uscsi_cdb[0] = 0x43 /* READ_TOC_CMD */;
 491         scmd.uscsi_cdb[2] = format & 0xf;
 492         scmd.uscsi_cdb[6] = trackno;
 493         scmd.uscsi_cdb[8] = buflen & 0xff;
 494         scmd.uscsi_cdb[7] = (buflen >> 8) & 0xff;
 495         scmd.uscsi_cdblen = 10;
 496         scmd.uscsi_bufaddr = (char *)buf;
 497         scmd.uscsi_buflen = buflen;
 498 
 499         if ((uscsi(fd, &scmd)) != 0) {
 500                 return (B_FALSE);
 501         }
 502 
 503         return (B_TRUE);
 504 }