cd-utils 1.0.0

Small interface to cd-rom library
Documentation
/*
 * Copyright (c) 1999-2003 Robert Woodcock <rcw@debian.org>
 * Copyright (c) 2009 Timur Birsh <taem@linukz.org>
 * This code is hereby licensed for public consumption under either the
 * GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice.

 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "discid.h"
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>

static int cddb_sum(int n)
{
	/* a number like 2344 becomes 2+3+4+4 (13) */
	int ret=0;

	while (n > 0) {
		ret = ret + (n % 10);
		n = n / 10;
	}

	return ret;
}


#define DISCID_STRING_BUFFER_SIZE 2048
#define MISC_STRING_BUFFER_SIZE 512

static char discid_string[DISCID_STRING_BUFFER_SIZE];
static char discid_error_message[MISC_STRING_BUFFER_SIZE];
static bool discid_string_new = true;
static char *command = "libcd-utils.a";
static bool verbose = false;

static void set_error_message(const char *command, const char *devicename, const char * perror_tag) {
	if (verbose) {
		fprintf(stderr, "%s: %s: ", command, devicename);
		perror(perror_tag);
	}

	snprintf(discid_error_message, MISC_STRING_BUFFER_SIZE - 1, "%s %s: %s", command, devicename, strerror(errno));
}

char *cdrom_get_error_message() {
	return discid_error_message;
}

static void append_discid_string(const char *str)
{
	if ((str == NULL) || (*str == '\0')) {
		return;
	}
	// int str_len = strlen(str);
	if (discid_string_new) {
		strcpy(discid_string, str);
		discid_string_new = false;
	}
	else {
		strcat(discid_string, str);
	}
}

static void append_discid_formatted(const char * format, ...) {
	if ((format == NULL) || (*format == '\0')) {
		return;
	}
	char str[MISC_STRING_BUFFER_SIZE];
	va_list argptr;
	va_start(argptr, format);
	vsprintf(str, format, argptr);
	va_end(argptr);
	append_discid_string(str);
}

char *cdrom_get_discid(const char *devicename, int musicbrainz)
{
	int len;
	int drive, i, totaltime;
	long int cksum=0;
	struct cdrom_tochdr hdr;
	struct cdrom_tocentry *TocEntry;
#if defined(__OpenBSD__) || defined(__NetBSD__)
	struct ioc_read_toc_entry t;
#elif defined(__APPLE__)
	dk_cd_read_disc_info_t discInfoParams;
#endif

	strcpy(discid_error_message, "");
	discid_error_message[MISC_STRING_BUFFER_SIZE - 1] = '\0';

	drive = open(devicename, O_RDONLY | O_NONBLOCK);
	if (drive < 0) {
		fprintf(stderr, "%s: %s: ", command, devicename);
		set_error_message(command, devicename, "open");
		return NULL;
	}

#if defined(__APPLE__)
	memset(&discInfoParams, 0, sizeof(discInfoParams));
	discInfoParams.buffer = &hdr;
	discInfoParams.bufferLength = sizeof(hdr);
	if (ioctl(drive, DKIOCCDREADDISCINFO, &discInfoParams) < 0
		|| discInfoParams.bufferLength != sizeof(hdr)) {
		set_error_message(command, devicename, "DKIOCCDREADDISCINFO");
		return NULL;
	}
#else
	if (ioctl(drive, CDROMREADTOCHDR, &hdr) < 0) {
		set_error_message(command, devicename, "CDROMREADTOCHDR");
		return NULL;
	}
#endif

	// unsigned char first = hdr.cdth_trk0;
	unsigned char last = hdr.cdth_trk1;

	len = (last + 1) * sizeof(struct cdrom_tocentry);

	TocEntry = malloc(len);
	if (!TocEntry) {
		set_error_message(command, devicename, "Can't allocate memory for TOC entries");
		return NULL;
	}
#if defined(__OpenBSD__) 
	t.starting_track = 0;
#elif defined(__NetBSD__)
	t.starting_track = 1;
#endif
#if defined(__OpenBSD__) || defined(__NetBSD__)
	t.address_format = CDROM_LBA;
	t.data_len = len;
	t.data = TocEntry;
	memset(TocEntry, 0, len);
	
	if (ioctl(drive, CDIOREADTOCENTRYS, (char *) &t) < 0) {
		set_error_message(command, devicename, "CDIOREADTOCENTRYS");
	}
#elif defined(__APPLE__)
	dk_cd_read_track_info_t trackInfoParams;
	memset( &trackInfoParams, 0, sizeof( trackInfoParams ) );
	trackInfoParams.addressType = kCDTrackInfoAddressTypeTrackNumber;
	trackInfoParams.bufferLength = sizeof( *TocEntry );
        
	for (i = 0; i < last; i++) {
		trackInfoParams.address = i + 1;
		trackInfoParams.buffer = &TocEntry[i];

		if (ioctl(drive, DKIOCCDREADTRACKINFO, &trackInfoParams) < 0) {
			set_error_message(command, devicename, "DKIOCCDREADTRACKINFO");
		}
	}

	/* MacOS X on G5-based systems does not report valid info for
	 * TocEntry[last-1].lastRecordedAddress + 1, so we compute the start
	 * of leadout from the start+length of the last track instead
	 */
	TocEntry[last].cdte_track_address = TocEntry[last-1].trackSize + TocEntry[last-1].trackStartAddress;
#else /* FreeBSD, Linux, Solaris */
	for (i=0; i < last; i++) {
		/* tracks start with 1, but I must start with 0 on OpenBSD */
		TocEntry[i].cdte_track = i + 1;
		TocEntry[i].cdte_format = CDROM_LBA;
		if (ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]) < 0) {
			set_error_message(command, devicename, "CDROMREADTOCENTRY");
			return NULL;
		}
	}

	TocEntry[last].cdte_track = CDROM_LEADOUT;
	TocEntry[last].cdte_format = CDROM_LBA;
	if (ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]) < 0) {
		set_error_message(command, devicename, "CDROMREADTOCENTRY");
		return NULL;
	}
#endif

#if defined(__FreeBSD__)
	TocEntry[i].cdte_track_address = ntohl(TocEntry[i].cdte_track_address);
#endif       

	for (i=0; i < last; i++) {
#if defined(__FreeBSD__)
		TocEntry[i].cdte_track_address = ntohl(TocEntry[i].cdte_track_address);
#endif
		cksum += cddb_sum((TocEntry[i].cdte_track_address + CD_MSF_OFFSET) / CD_FRAMES);
	}

	totaltime = ((TocEntry[last].cdte_track_address + CD_MSF_OFFSET) / CD_FRAMES) -
		    ((TocEntry[0].cdte_track_address + CD_MSF_OFFSET) / CD_FRAMES);

	/* print discid */
	if (! musicbrainz) {
		append_discid_formatted("%08lx ", (cksum % 0xff) << 24 | totaltime << 8 | last);
	}

	/* print number of tracks */
	append_discid_formatted("%d", last);

	/* print frame offsets of all tracks */
	for (i = 0; i < last; i++) {
		append_discid_formatted(" %d", TocEntry[i].cdte_track_address + CD_MSF_OFFSET);
	}

	if (musicbrainz) {
		append_discid_formatted(" %d", TocEntry[last].cdte_track_address + CD_MSF_OFFSET);
	} else {
		/* print length of disc in seconds */
		append_discid_formatted(" %d", (TocEntry[last].cdte_track_address + CD_MSF_OFFSET) / CD_FRAMES);
	}

	free(TocEntry);
	close(drive);
	return discid_string;
}

#ifdef STANDALONE

int main(int argc, char *argv[])
{
	int musicbrainz=0;
	char *devicename=DEVICE_NAME;

	command=argv[0];

	if (argc >= 2 && ! strcmp(argv[1], "--musicbrainz")) {
		musicbrainz = 1;
		argc--;
		argv++;
	}

	if (argc == 2) {
		devicename = argv[1];
	} else if (argc > 2) {
		fprintf(stderr, "Usage: %s [--musicbrainz] [devicename]\n",
			command);
		exit(1);
	}

	char *discid = cdrom_get_discid(devicename, musicbrainz);
	printf("%s\n", discid);
	
	return 0;
}

#endif