/*
* 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));
}