/* Setcd.c: Set various flags to control the behaviour of your cdrom device.
   (c) 1997 David A. van Leeuwen

   $Id: setcd.c,v 1.4 1999/08/24 19:07:34 david Exp $

     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>		/* tolower */
#include <unistd.h>		/* lseek, read */
#include <string.h>		/* strncmp */
#include <sys/ioctl.h>		/* ioctl */
#include <linux/cdrom.h>
#ifndef CDC_CLOSE_TRAY		/* moved into cdrom.h... */
#include <linux/ucdrom.h>
#endif
#include <linux/iso_fs.h>

#define REVISION "$Revision: 1.4 $"

void usage(FILE * f, char * s) 
{
     fprintf(f, "Usage: %s [-hirs] [-ceflot 0|1] [-dx arg] [device] ...\n\n\t"
	     "-h   print this message and copyright\n\t"
	     "-i   print information about cdrom device\n\t"
	     "-s   print status of cdrom device options\n\t"
	     "-r   reset option flags to preferred default\n\t"
	     "-c * set auto-close flag: close tray if cdrom device is opened\n\t"
	     "-e * set auto-eject/auto-close behaviour\n\t"
	     "-f * use file flag O_NONBLOCK in open() call\n\t"
	     "-l * lock door if cdrom device is opened\n\t"
	     "-o * set auto_open flag: open tray if cdrom device is released\n\t"
	     "-t * enable cdrom type checking (audio/data)\n\t"
	     "-d n select disc number n in jukebox\n\t"
	     "-x n set speed of cdrom n times 150 KB/s (normal audio)\n", s);
}

#define SHOW(bit, text) if(bit & mask) \
printf("  %-20s %s\n", text ":", (bit & flags) ? "set" : "cleared");

void print_options(int flags, int mask)
{
     SHOW(CDO_AUTO_CLOSE, "Auto close tray");
     SHOW(CDO_AUTO_EJECT, "Auto open tray");
     SHOW(CDO_USE_FFLAGS, "Use O_NONBLOCK flag");
     SHOW(CDO_LOCK, "Lock tray");
     SHOW(CDO_CHECK_TYPE, "Check CD type");
}
#undef SHOW

void print_audio_info(int fd)
{
     struct cdrom_tochdr head;
     struct cdrom_tocentry entry;
     int last, first;
     if (ioctl(fd, CDROMREADTOCHDR, &head)<0) {
	  perror("Can't read Table of Contents header");
	  return;
     }
     last = head.cdth_trk1; first = head.cdth_trk0;
     printf("    %d Track%s, %d--%d\n", last-first+1, (last==first) ? "" : "s",
	    first, last);
     entry.cdte_format=CDROM_MSF;
     entry.cdte_track = 0xAA;
     if (ioctl(fd, CDROMREADTOCENTRY, &entry)<0) {
	  perror("can't read last track entry");
	  return;
     }
     printf("    Total playing time %d minutes and %d seconds\n", 
	    entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second);
}  

char * stripped(char * s, int size, int lower)
{
     char *i, * end = s+size-1;
     while (*end == ' ') --end;
     if (end == s+size-1) *end='\0';
     else *++end = '\0';
     if (lower) for (i=s+1; i<end; i++) *i=tolower(*i);
     return s;
}

void print_data_info(int fd)
{
     struct iso_primary_descriptor * vdp;
     char buf[ISOFS_BLOCK_SIZE];
     int sector;
     for(sector=16; sector < 100; sector++) {
	  lseek(fd, sector*ISOFS_BLOCK_SIZE, SEEK_SET);
	  read(fd, buf, ISOFS_BLOCK_SIZE);
	  vdp = (struct iso_primary_descriptor *) &buf;
	  if (!strncmp(vdp->id, ISO_STANDARD_ID, sizeof vdp->id)) {
	       /* found an iso-cdrom */
	       printf("    Volume name: %s\n", 
		      stripped(vdp->volume_id, 32, 1));
	       printf("    Publisher: %s\n", 
		      stripped(vdp->publisher_id, 128, 0));
	       printf("    Data preparer: %s\n", 
		      stripped(vdp->preparer_id, 128, 0));
	       break;
	  }
     }
}

void print_info(int fd)
{
     int status = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT);
     if (status<0) {
	  perror("can't read drive status");
	  fprintf(stderr, "%d\n", status);
	  exit (-8);
     }
     switch(status) {
     case CDS_NO_INFO: 
	  printf("  No information on drive status is available\n");
	  break;
     case CDS_NO_DISC:
	  printf("  No disc is inserted\n");
	  break;
     case CDS_TRAY_OPEN:
	  printf("  CD tray is open\n");
	  break;
     case  CDS_DRIVE_NOT_READY: 
	  printf("  Drive is not ready\n");
	  break;
     case CDS_DISC_OK:
	  printf("  Disc found in drive:");
	  status=ioctl(fd,  CDROM_DISC_STATUS);
	  if (status < 0) {
	       fprintf(stderr, "%d\n", status);
	       break;
	  }
	  switch (status) {
#ifdef CDS_MIXED
	  case CDS_MIXED:
	       printf(" mixed type CD (data/audio)\n");
	       print_audio_info(fd);
	       print_data_info(fd);
	       break;
#endif
	  case CDS_AUDIO:
	       printf(" audio disc\n");
	       print_audio_info(fd);
	       break;
	  case CDS_DATA_1:
	  case CDS_DATA_2:
	       printf(" data disc type %d\n", status-CDS_DATA_1+1);
	       print_data_info(fd);
	       break;
	  case CDS_XA_2_1:
	  case CDS_XA_2_2:
	       printf(" XA data disc type %d\n", status-CDS_XA_2_1+1);
	       print_data_info(fd);
	       break;
	  default:
	       printf("Unknown status %d\n", status);
	  }
	  break;
     default:
	  printf("  Unknow drive status return value %d\n", status);
     }
}

int main(int ac, char ** av) 
{
     char * device = "/dev/cdrom";
     int fd, c;
     int status=0, reset=0, info=0, typecheck=-1, speed=-1,
	  auto_close=-1, auto_open=-1, fflags=-1, lock=-1, disc=-1;
     int set=0, clear=0, flags=0;
     extern int optind, optopt;
     extern char * optarg;
     if (ac == 1) {
	  usage(stderr, av[0]);
	  exit(-1);
     }
     while((c=getopt(ac, av, "c:d:e:f:hil:o:rst:x:"))!=EOF) {
	  switch(c) {
	  case 'c': auto_close=atoi(optarg); break;
	  case 'd': disc=atoi(optarg); break;
	  case 'f': fflags=atoi(optarg); break;
	  case 'e': auto_close=auto_open=atoi(optarg); break;
	  case 'i': info=1; break;
	  case 'l': lock=atoi(optarg); break;
	  case 'o': auto_open=atoi(optarg); break;
	  case 'r': reset=1;		/* fall into next line */
	  case 's': status=1; break;
	  case 't': typecheck=atoi(optarg); break;
	  case 'x': speed=atoi(optarg); break;
	  case 'h':
	       printf("Setcd. Control the behaviour of a cdrom device.\n"
		      "(c) 1997 David A. van Leeuwen\n"
		      REVISION "\n\n");
	       usage(stdout, av[0]);
	       exit(0);
	  default:
	       usage(stderr, av[0]);
	       exit(-1);
	  }
     }
     do {
	  if (ac > optind) device = av[optind];
	  fd = open(device, O_RDONLY | O_NONBLOCK);
	  if (fd < 0) {
	       perror("Can't open device");
	       exit(-2);
	  } 
	  printf("%s:\n", device);
#define FLAG(i,opt) if (i>=0) { set += opt*(i>0); clear += opt*!i; }
	  if (reset) {
	       set = CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK | 
		    CDO_CHECK_TYPE;
	       clear = CDO_AUTO_EJECT; /* nasty behaviour */
	  }
	  FLAG(auto_close, CDO_AUTO_CLOSE);
	  FLAG(auto_open, CDO_AUTO_EJECT);
	  FLAG(fflags, CDO_USE_FFLAGS);
	  FLAG(lock, CDO_LOCK);
	  FLAG(typecheck, CDO_CHECK_TYPE);
#undef FLAG
	  if (set && (flags=ioctl(fd, CDROM_SET_OPTIONS, set))<0) {
	       perror("can't set options");
	       exit(-3);
	  }
	  if (clear && (flags=ioctl(fd, CDROM_CLEAR_OPTIONS, clear))<0) {
	       perror("can't clear options");
	       exit(-4);
	  }
	  if (!flags && status && (flags=ioctl(fd, CDROM_SET_OPTIONS, 0))<0) {
	       perror("can't get options");
	       exit(-5);
	  }
	  if (set || clear || status)
	       print_options(flags, set | clear | ~0 * status);
	  if (speed>=0) {
	       if (ioctl(fd, CDROM_SELECT_SPEED, speed)<0) {
		    perror("can't set speed");
		    exit(-6);
	       } else {
		    printf("  Speed set at ");
		    if (!speed) printf("maximum rate / normal audio rate\n");
		    else printf("%d KB/s (%dx)\n", 150*speed, speed);
	       }
	  }
	  if (disc >= 0) {
	       if ((disc=ioctl(fd, CDROM_SELECT_DISC, disc)) < 0) {
		    perror("can't select disc");
		    exit(-7);
	       } else
		    printf("  Disc in slot %d selected\n", disc);
	  } 
	  if (info) print_info(fd);
	  close(fd);
     } while (ac > ++optind);
     return 0;
}

/* It was not easy to find variable `c-file-style' in emacs info. In fact,
   I found it in some other .c file. 
 * Local variables:
 * c-file-style: "K&R"
 * compile-command: "gcc -Wall setcd.c -o setcd"
 * End:
 */
