OpenSolaris_b135/cmd/audio/utilities/AudioUnixfile.cc

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1993-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/t_lock.h>

#include <AudioDebug.h>
#include <AudioUnixfile.h>
#include <libaudio.h>
#include <audio_hdr.h>
#include <audio/au.h>

// class AudioUnixfile methods

// Constructor with pathname and mode arg
AudioUnixfile::
AudioUnixfile(
	const char		*path,		// pathname
	const FileAccess	acc):		// access mode
	AudioStream(path), fd(-1), block(TRUE), mode(acc),
	infostring(new char[1]), infolength(1)
{
	infostring[0] = '\0';
}

// Destructor
AudioUnixfile::
~AudioUnixfile()
{
	// If the file is open, close it
	if (opened())
		(void) Close();

	// Deallocate the dynamic storage
	delete infostring;
}

// Generic open with search path routine just calls default Open()
AudioError AudioUnixfile::
OpenPath(
	const char *)
{
	return (Open());
}

// Decode an audio file header
// This routine reads the audio file header and decodes it.
//
// This method should be specialized by subclasses that are not files,
// like devices for instance.
//
// XXX - this routine should be rewritten for C++
AudioError AudioUnixfile::
decode_filehdr()
{
	Boolean		saveblock;	// saved state of the blocking i/o flag
	AudioHdr	hdr_local;	// local copy of header
	Audio_hdr	ohdr;		// XXX - old libaudio hdr
	au_filehdr_t	fhdr;
	char		*ibuf;
	int		file_type;
	int		infosize;
	int		cnt;
	struct stat	st;
	AudioError	err;

	// If fd is not open, or file header already decoded, skip it
	if (!isfdset() || opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// Stat the file, to see if it is a regular file
	if (fstat(getfd(), &st) < 0)
		return (RaiseError(AUDIO_UNIXERROR));

	// Make sure the file is not set for blocking i/o
	saveblock = GetBlocking();
	if (!saveblock)
		SetBlocking(TRUE);

	// Read the file header, but not the info field
	// XXX - Should use C++ input method
	cnt = read(getfd(), (char *)&fhdr, sizeof (fhdr));
	if (cnt != sizeof (fhdr)) {
		return (RaiseError(AUDIO_UNIXERROR));
	}

	// Check the validity of the header and get the size of the info field
	err = (AudioError) audio_decode_filehdr(getfd(), (unsigned char *)&fhdr,
	    &file_type, &ohdr, &infosize);
	if (err != AUDIO_SUCCESS)
		return (RaiseError(err));

	// Allocate and read in the info field
	ibuf = new char[infosize];
	cnt = read(getfd(), ibuf, infosize);
	if (cnt != infosize) {
		delete ibuf;
		return (RaiseError(AUDIO_UNIXERROR));
	}
	SetBlocking(saveblock);		// Restore the saved blocking i/o state

	// XXX - convert from libaudio header
	hdr_local = GetHeader();
	hdr_local.sample_rate = ohdr.sample_rate;
	hdr_local.samples_per_unit = ohdr.samples_per_unit;
	hdr_local.bytes_per_unit = ohdr.bytes_per_unit;
	hdr_local.channels = ohdr.channels;
	hdr_local.encoding = (AudioEncoding) ohdr.encoding;
	hdr_local.endian = BIG_ENDIAN; // Files are always written in
					// big endian.

	err = SetHeader(hdr_local);
	if (err != AUDIO_SUCCESS) {
		delete ibuf;
		return (RaiseError(err));
	}
	SetInfostring(ibuf, infosize);
	delete ibuf;

	// Only trust the file size for regular files
	if (S_ISREG(st.st_mode)) {
		setlength(GetHeader().Bytes_to_Time(
		    st.st_size - infosize - sizeof (au_filehdr_t)));

		// Sanity check
		if ((ohdr.data_size != AUDIO_UNKNOWN_SIZE) &&
		    (GetLength() != GetHeader().Bytes_to_Time(ohdr.data_size)))
			PrintMsg(_MGET_(
			    "AudioUnixfile: header/file size mismatch"));

		// always consider it to be unknown if not reading a real file
		// since there's no real way to verify if the header is
		// correct.
	} else {
		setlength(AUDIO_UNKNOWN_TIME);
	}

	// set flag for opened() test
	filehdrset = TRUE;

	return (AUDIO_SUCCESS);
}

// Write an audio file header
// This routine encodes the audio file header and writes it out.
// XXX - It assumes that the file pointer is set to the start of the file.
//
// This method should be specialized by subclasses that are not files,
// like devices for instance.
//
// XXX - this routine should be rewritten for C++
AudioError AudioUnixfile::
encode_filehdr()
{
	Boolean		saveblock;	// saved state of the blocking i/o flag
	AudioHdr	hdr_local;	// local copy of header
	Audio_hdr	ohdr;		// XXX - old libaudio hdr
	AudioError	err;

	// If fd is not open, or file header already written, skip it
	if (!isfdset() || opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// XXX - Set up the libaudio hdr
	hdr_local = GetHeader();
	hdr_local.endian = BIG_ENDIAN; // Files are always written big endian.
	err = SetHeader(hdr_local);
	if (err != AUDIO_SUCCESS) {
		return (RaiseError(err));
	}

	ohdr.sample_rate = hdr_local.sample_rate;
	ohdr.samples_per_unit = hdr_local.samples_per_unit;
	ohdr.bytes_per_unit = hdr_local.bytes_per_unit;
	ohdr.channels = hdr_local.channels;
	ohdr.encoding = hdr_local.encoding;
	if (Undefined(GetLength()))
		ohdr.data_size = AUDIO_UNKNOWN_SIZE;
	else
		ohdr.data_size = (uint_t)GetHeader().Time_to_Bytes(GetLength());

	/* Make sure the file is not set for blocking i/o */
	saveblock = GetBlocking();
	if (!saveblock)
		SetBlocking(TRUE);

	// XXX - Should use C++ output method
	err = (AudioError) audio_write_filehdr(getfd(), &ohdr, FILE_AU,
	    infostring, infolength);

	// set flag for opened() test
	if (err == AUDIO_SUCCESS)
		filehdrset = TRUE;

	SetBlocking(saveblock);		// Restore the saved blocking i/o state
	return (RaiseError(err));
}

// Set a file blocking/non-blocking
// This method should be subclassed by objects that always block (eg, files)
void AudioUnixfile::
SetBlocking(
	Boolean		b)			// FALSE to set non-blocking
{
	int		flag;

	// If the file is open, set blocking/non-blocking now
	if (isfdset()) {
		flag = fcntl(getfd(), F_GETFL, 0);
		if ((flag < 0) && (errno == EOVERFLOW || errno == EINVAL)) {
			RaiseError(AUDIO_UNIXERROR, Fatal,
			    (char *)"Large File");
		} else if (b) {
			flag &= ~(O_NDELAY | O_NONBLOCK);	// set blocking
		} else {
			flag |= O_NONBLOCK;		// set non-blocking
		}
		if (fcntl(getfd(), F_SETFL, flag) < 0) {
			RaiseError(AUDIO_UNIXERROR, Warning);
		}
	}
	// Set the blocking flag (this may affect the Open() behavior)
	block = b;
}

// Return a pointer to the info string
// XXX - returns a pointer to the string stored in the object
// XXX - assumes ASCII data
char *const AudioUnixfile::
GetInfostring(
	int&		len) const		// returned length of string
{
	len = infolength;
	return (infostring);
}

// Set the info string
void AudioUnixfile::
SetInfostring(
	const char	*str,			// new info string
	int		len)			// length of string
{
	// If length defaulted, assume an ASCII string
	if (len == -1)
		len = strlen(str) + 1;
	delete infostring;
	infostring = new char[len];
	infolength = len;
	(void) memcpy(infostring, str, len);
}

// Close file
AudioError AudioUnixfile::
Close()
{
	// If the file is open, close it
	if (isfdset()) {
		if (close(getfd()) < 0)
			return (RaiseError(AUDIO_UNIXERROR));
	} else {
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
	}

	// Init important values, in case the file is reopened
	setfd(-1);
	filehdrset = FALSE;
	(void) SetReadPosition((Double)0., Absolute);
	(void) SetWritePosition((Double)0., Absolute);
	return (AUDIO_SUCCESS);
}

// Read data from underlying file into specified buffer.
// No data format translation takes place.
// The object's read position is not updated (subclasses can change this)
AudioError AudioUnixfile::
ReadData(
	void*		buf,		// destination buffer address
	size_t&		len,		// buffer length (updated)
	Double&		pos)		// start position (updated)
{
	off_t		offset;
	off_t		cnt;
	AudioError	err;

	// Save buffer size and zero transfer count
	cnt = (off_t)len;
	len = 0;

	// Cannot read if file is not open
	if (!opened() || !mode.Readable())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Position must be valid
	if (Undefined(pos) || (pos < 0.) || (cnt < 0))
		return (RaiseError(AUDIO_ERR_BADARG));

	// Position the file pointer to the right place
	err = seekread(pos, offset);
	if (err != AUDIO_SUCCESS)
		return (err);

	// Check for EOF
	if (pos >= GetLength()) {
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}

	// Zero-length reads are finished
	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
		err = AUDIO_SUCCESS;
		err.sys = AUDIO_COPY_ZERO_LIMIT;
		return (err);
	}

	// Read as much data as possible
	cnt = read(fd, (char *)buf, (int)cnt);
	if (cnt < 0) {
		if (errno == EOVERFLOW) {
			perror("read");
			exit(1);
		} else if ((errno == EINTR) ||
		    (((errno == EWOULDBLOCK) || (errno == EAGAIN)) &&
		    !GetBlocking())) {
		// Is this an interrupted or failed non-blocking request?
			err = AUDIO_SUCCESS;
			err.sys = AUDIO_COPY_SHORT_INPUT;
			return (err);
		}
		return (RaiseError(AUDIO_UNIXERROR));
	}

	// End-of-file?
	if ((cnt == 0) && GetBlocking()) {
		if (isDevice() || isPipe()) {
			AUDIO_DEBUG((1,
			    "Zero-length blocking device/pipe read?!\n"));
		}
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}
	err = AUDIO_SUCCESS;
	if (cnt == 0) {
		err.sys = AUDIO_COPY_SHORT_INPUT;
	}

	// Return the updated byte count and position
	len = (size_t)cnt;
	if (GetHeader().Bytes_to_Bytes(cnt) != len) {
		AUDIO_DEBUG((1,
		    "Read returned a partial sample frame?!\n"));
	}
	pos = GetHeader().Bytes_to_Time(offset + len);

	// Check to see if the endian is right.
	coerceEndian((unsigned char *)buf, len, localByteOrder());

	return (err);
}

// Write data to underlying file from specified buffer.
// No data format translation takes place.
// The object's write position is not updated (subclasses can change this)
AudioError AudioUnixfile::
WriteData(
	void*		buf,		// source buffer address
	size_t&		len,		// buffer length (updated)
	Double&		pos)		// start position (updated)
{
	off_t		offset;
	off_t		cnt;
	AudioError	err;

	// Save buffer size and zero transfer count
	cnt = (off_t)len;
	len = 0;

	// Cannot write if file is not open
	if (!opened() || !mode.Writeable())
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Position must be valid
	if (Undefined(pos) || (pos < 0.) || (cnt < 0))
		return (RaiseError(AUDIO_ERR_BADARG));

	// Zero-length writes are easy
	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
		err = AUDIO_SUCCESS;
		err.sys = AUDIO_COPY_ZERO_LIMIT;
		return (err);
	}

	// Position the file pointer to the right place
	err = seekwrite(pos, offset);
	if (err != AUDIO_SUCCESS)
		return (err);

	// Make sure data is in target's endian format before writing.
	// This conversion is done inplace so we need to change back.
	// We assume that the data in buf is in localByteOrder.
	// Only files should have order issues.
	if (localByteOrder() != GetHeader().endian)
		coerceEndian((unsigned char *)buf, (size_t)cnt, SWITCH_ENDIAN);

	// Write as much data as possible
	err = AUDIO_SUCCESS;
	cnt = write(fd, (char *)buf, (int)cnt);
	if (cnt < 0) {
		if (errno == EFBIG) {
			perror("write");
			exit(1);
		} else if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
			// Is this a failed non-blocking request?
			err.sys = AUDIO_COPY_SHORT_OUTPUT;
			return (err);
		}
		return (RaiseError(AUDIO_UNIXERROR));
	}
	if (cnt == 0)
		err.sys = AUDIO_COPY_SHORT_OUTPUT;

	// Switch the endian back if local order doesn't match target order.
	if (localByteOrder() != GetHeader().endian)
		coerceEndian((unsigned char *)buf, (size_t)cnt, SWITCH_ENDIAN);

	// Return the updated byte count and position
	len = (size_t)cnt;
	pos = GetHeader().Bytes_to_Time(offset + len);

	// If the current position is beyond old EOF, update the size
	if (!Undefined(GetLength()) && (pos > GetLength())) {
		setlength(pos);
	}

	return (AUDIO_SUCCESS);
}

// Seek in input stream
// Ordinary streams (ie, pipes and devices) cannot be rewound.
// A forward seek in them consumes data by reading it.
//
// This method should be specialized by subclasses that can actually seek,
// like regular files for instance.
//
AudioError AudioUnixfile::
seekread(
	Double		pos,		// position to seek to
	off_t&		offset)		// returned byte offset
{
	char		*bufp;		// temporary input buffer
	size_t		bufl;		// input buffer size
	size_t		cnt;		// input byte count
	long		icnt;		// read size
	Boolean		saveblock;	// saved state of the blocking i/o flag
	Double		buflen;
	AudioError	err;

	offset = GetHeader().Time_to_Bytes(pos);
	pos -= ReadPosition();

	// If the seek is backwards, do nothing
	if (pos < 0.)
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// If the seek is to the current position, then do nothing.
	icnt = GetHeader().Time_to_Bytes(pos);
	if (icnt == 0)
		return (AUDIO_SUCCESS);

	// The seek is determinate and forward.
	// We'll have to consume data to get there.
	// First allocate a buffer to stuff the data into.
	// Then set the stream for blocking i/o (saving the old state).
	buflen = max(pos, 1.);
	bufl = (size_t)GetHeader().Time_to_Bytes(buflen);
	bufp = new char[bufl];
	if (bufp == 0) {		// allocation error, try a smaller buf
		bufl = (size_t)sysconf(_SC_PAGESIZE);
		bufp = new char[bufl];
		if (bufp == 0)
			return (RaiseError(AUDIO_UNIXERROR));
	}
	// XXX - May have to realign to partial frame count!

	saveblock = GetBlocking();
	if (!saveblock)
		SetBlocking(TRUE);

	// Loop until the seek is satisfied (or an error occurs).
	do {
		// Limit the read to keep from going too far
		cnt = (icnt >= (long)bufl) ? bufl : (size_t)icnt;
		err = Read(bufp, cnt);
		if (err != AUDIO_SUCCESS)
			break;
		icnt -= (long)cnt;
	} while (icnt > 0);

	SetBlocking(saveblock);		// Restore the saved blocking i/o state
	delete bufp;			// Free the temporary buffer
	return (RaiseError(err));
}

// Seek in output stream
// Ordinary streams (ie, pipes and devices) cannot be rewound.
// A forward seek in them writes NULL data.
//
// This method should be specialized by subclasses that can actually seek,
// like regular files for instance.
//
AudioError AudioUnixfile::
seekwrite(
	Double		pos,		// position to seek to
	off_t&		offset)		// returned byte offset
{
	char		*bufp;		// temporary output buffer
	size_t		bufl;		// output buffer size
	size_t		cnt;		// output byte count
	long		ocnt;		// write size
	Boolean		saveblock;	// saved state of the blocking i/o flag
	Double		buflen;
	AudioError	err;

	offset = GetHeader().Time_to_Bytes(pos);
	pos -= WritePosition();

	// If the seek is backwards, do nothing
	if (pos < 0.)
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// If the seek is to the current position, then do nothing.
	ocnt = GetHeader().Time_to_Bytes(pos);
	if (ocnt == 0)
		return (AUDIO_SUCCESS);

	// The seek is determinate and forward.
	// We'll have to produce NULL data to get there.
	// XXX - not implemented correctly yet
	buflen = max(pos, 1.);
	bufl = (size_t)GetHeader().Time_to_Bytes(buflen);
	bufp = new char[bufl];
	if (bufp == 0) {		// allocation error, try a smaller buf
		bufl = (size_t)sysconf(_SC_PAGESIZE);
		bufp = new char[bufl];
		if (bufp == 0)
			return (RaiseError(AUDIO_UNIXERROR));
	}

	// XXX - May have to realign to partial frame count!
	saveblock = GetBlocking();
	if (!saveblock)
		SetBlocking(TRUE);

	// Loop until the seek is satisfied (or an error occurs).
	do {
		// Limit the write to keep from going too far
		cnt = (ocnt >= (long)bufl) ? bufl : (size_t)ocnt;
		err = Write(bufp, cnt);
		if (err != AUDIO_SUCCESS)
			break;
		ocnt -= (long)cnt;
	} while (ocnt > 0);

	SetBlocking(saveblock);		// Restore the saved blocking i/o state
	delete bufp;			// Free the temporary buffer
	return (RaiseError(err));
}