OpenSolaris_b135/cmd/audio/utilities/AudioFile.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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>

#include <AudioFile.h>
#include <AudioLib.h>
#include <AudioDebug.h>
#include <libaudio.h>
#include <audio_hdr.h>

// XX64  This should go away when <sys/mman.h> gets fixed.
extern "C" int madvise(caddr_t, size_t, int);

// class AudioFile methods


// Initialize temporary file params
#define	TMPDIR		"/tmp"
#define	TMPFILE		"/audiotoolXXXXXX"
static char		*tmpdir = NULL;
static const char	*tmpname = "(temporary file)";
static const FileAccess	tmpmode = ReadWrite;
static const VMAccess	defaccess = SequentialAccess;

// Initialize default access mode, used when a filename is supplied
const FileAccess	AudioFile::defmode = ReadOnly;

// Default audio file path prefix environment variable
const char *AudioFile::AUDIO_PATH = "AUDIOPATH";


// Constructor with no arguments opens a read/write temporary file
AudioFile::
AudioFile():
	AudioUnixfile(tmpname, tmpmode),
	hdrsize(0), seekpos(0), origlen(0.), mapaddr(0), maplen(0),
	vmaccess(defaccess)
{
}

// Constructor with pathname and optional mode arg
AudioFile::
AudioFile(
	const char		*path,		// filename
	const FileAccess	acc):		// access mode
	AudioUnixfile(path, acc),
	hdrsize(0), seekpos(0), origlen(0.), mapaddr(0), maplen(0),
	vmaccess(defaccess)
{
}

// Destructor must call the local Close() routine
AudioFile::
~AudioFile()
{
	// If the file was open, close it
	if (opened())
		(void) Close();
}

// Set a default temporary file directory
AudioError AudioFile::
SetTempPath(
	const char	*path)
{
	struct stat	st;

	// Verify intended path
	if ((stat(path, &st) < 0) ||
	    !S_ISDIR(st.st_mode) ||
	    (access(path, W_OK) < 0)) {
		errno = ENOTDIR;
		return (AUDIO_UNIXERROR);
	}

	if (tmpdir != NULL)
		(void) free(tmpdir);
	tmpdir = (char *)malloc(strlen(path) + 1);
	(void) strcpy(tmpdir, path);
	return (AUDIO_SUCCESS);
}


// Create a named file according to the current mode setting
AudioError AudioFile::
createfile(
	const char	*path)			// pathname or 0
{
	char		*tmpf;
	char		*tmpstr;
	int		openmode;
	int		desc;
	AudioError	err;

	// Convert the open mode to an int argument for open()
	openmode = GetAccess();

	// Was the header properly set?
	if (!hdrset())
		return (RaiseError(AUDIO_ERR_BADHDR));

	// Can't create if already opened or if mode or name not set
	if ((openmode == -1) || opened() || (strlen(path) == 0))
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// If temporary file, create and unlink it.
	if (strcmp(path, tmpname) == 0) {
		// Construct the temporary file path
		tmpstr = (char *)malloc(1 + strlen(TMPFILE) +
		    strlen((tmpdir == NULL) ? TMPDIR : tmpdir));
		(void) sprintf(tmpstr, "%s%s",
		    (tmpdir == NULL) ? TMPDIR : tmpdir, TMPFILE);
		tmpf = mktemp(tmpstr);

		// Open the temp file and unlink it
		err = createfile(tmpf);
		if ((err == AUDIO_SUCCESS) && (unlink(tmpf) < 0)) {
			(void) Close();
			err = RaiseError(AUDIO_UNIXERROR, Warning);
		}
		(void) free(tmpstr);
		return (err);
	}

	// Create the file
	desc = open(path, openmode | O_CREAT | O_TRUNC, 0666);
	if ((desc < 0) && (errno == EOVERFLOW)) {
		return (RaiseError(AUDIO_UNIXERROR, Fatal,
		    (char *)"Large File"));
	} else if (desc < 0) {
		return (RaiseError(AUDIO_UNIXERROR));
	}

	// Set the file descriptor (this marks the file open)
	setfd(desc);

	// Write the file header with current (usually unknown) size
	err = encode_filehdr();
	if (err != AUDIO_SUCCESS) {
		setfd(-1);
		(void) close(desc);		// If error, remove file
		(void) unlink(path);
		return (err);
	}

	// Save the length that got written, then set it to zero
	origlen = GetLength();
	setlength(0.);

	// Set the size of the file header
	hdrsize = lseek(desc, (off_t)0, SEEK_CUR);
	if (hdrsize < 0) {
		setfd(-1);
		(void) close(desc);		// If error, remove file
		(void) unlink(path);
		return (err);
	}
	seekpos = 0;

	return (AUDIO_SUCCESS);
}

// Create a file whose name is already set, according to the mode setting
AudioError AudioFile::
Create()
{
	return (createfile(GetName()));
}

// Open a file whose name is set
AudioError AudioFile::
Open()
{
	return (OpenPath(NULL));
}

// Open a file, using the specified path prefixes
AudioError AudioFile::
OpenPath(
	const char	*path)
{
	char		*filename;
	int		flen;
	char		*prefix;
	char		*str;
	char		*wrk;
	char		*pathname;
	int		openmode;
	AudioError	err;

	// Convert the open mode to an int argument for open()
	openmode = GetAccess();
	filename = GetName();
	flen = strlen(filename);

	// Can't open if already opened or if mode or name not set
	if ((openmode == -1) || opened() || (strlen(filename) == 0))
		return (RaiseError(AUDIO_ERR_NOEFFECT));

	// Search path:
	//	1) try name: if not found and not readonly:
	//		if Append mode, try creating it
	//	2) if name is a relative pathname, and 'path' is not NULL:
	//		try every path prefix in 'path'

	err = tryopen(filename, openmode);
	if (!err)
		return (AUDIO_SUCCESS);
	if (GetAccess().Writeable() || (filename[0] == '/')) {
		// If file is non-existent and Append mode, try creating it.
		if ((err == AUDIO_UNIXERROR) && (err.sys == ENOENT) &&
		    GetAccess().Append() && hdrset()) {
			return (Create());
		}
		return (RaiseError(err));
	}

	// Try path as an environment variable name, else assume it is a path
	str = (path == NULL) ? NULL : getenv(path);
	if (str == NULL)
		str = (char *)path;

	if (str != NULL) {
		// Make a copy of the path, to parse it
		wrk = new char[strlen(str) + 1];
		(void) strcpy(wrk, str);
		str = wrk;

		// Try each component as a path prefix
		for (prefix = str;
		    (prefix != NULL) && (prefix[0] != '\0');
		    prefix = str) {
			str = strchr(str, ':');
			if (str != NULL)
				*str++ = '\0';
			pathname = new char[strlen(prefix) + flen + 2];
			(void) sprintf(pathname, "%s/%s", prefix, filename);
			err = tryopen(pathname, openmode);
			delete pathname;
			switch (err) {
			case AUDIO_SUCCESS:	// found the file
				delete wrk;
				return (RaiseError(err));
			// XXX - if file found but not audio, stop looking??
			}
		}
		delete wrk;
	}
	// Can't find file.  Return the original error condition.
	return (RaiseError(tryopen(filename, openmode)));
}

// Attempt to open the given audio file
AudioError AudioFile::
tryopen(
	const char	*pathname,
	int		openmode)
{
	struct stat	st;
	int		desc;
	AudioError	err;

	// If the name is changing, set the new one
	if (pathname != GetName())
		SetName(pathname);

	// Does the file exist?
	if (stat(pathname, &st) < 0)
		return (AUDIO_UNIXERROR);

	// If not a regular file, stop right there
	if (!S_ISREG(st.st_mode))
		return (AUDIO_ERR_BADFILEHDR);

	// Open the file and check that it's an audio file
	desc = open(GetName(), openmode);
	if ((desc < 0) && (errno == EOVERFLOW)) {
		return (RaiseError(AUDIO_UNIXERROR, Fatal,
		    (char *)"Large File"));
	} else if (desc < 0) {
		return (AUDIO_UNIXERROR);
	}

	// Set the file descriptor (this marks the file open)
	setfd(desc);

	err = decode_filehdr();
	if (err != AUDIO_SUCCESS) {
		(void) close(desc);
		setfd(-1);
		return (err);
	}

	// Save the length of the data and the size of the file header
	origlen = GetLength();
	hdrsize = (off_t)lseek(desc, (off_t)0, SEEK_CUR);
	if (hdrsize < 0) {
		(void) close(desc);
		setfd(-1);
		return (err);
	}
	seekpos = 0;

	// If this is ReadOnly file, mmap() it.  Don't worry if mmap() fails.
	if (!GetAccess().Writeable()) {
		maplen = st.st_size;

		/*
		 * Can't mmap LITTLE_ENDIAN as they are converted in
		 * place.
		 */
		if (localByteOrder() == BIG_ENDIAN) {
			if ((mapaddr = (caddr_t)mmap(0, (int)maplen, PROT_READ,
				MAP_SHARED, desc, 0)) != (caddr_t)-1) {
				// set default access method
				(void) madvise(mapaddr, (unsigned int)maplen,
				    (int)GetAccessType());
			} else {
				(void) RaiseError(AUDIO_UNIXERROR, Warning,
				    (char *)"Could not mmap() file");
				mapaddr = 0;
				maplen = 0;
			}
		} else {
			mapaddr = 0;
			maplen = 0;
		}
	}
	return (AUDIO_SUCCESS);
}

// set VM access hint for mmapped files
AudioError AudioFile::
SetAccessType(VMAccess vmacc)
{
	if (!opened()) {
		return (AUDIO_ERR_NOEFFECT);
	}

	if (mapaddr == 0) {
		return (AUDIO_ERR_NOEFFECT);
	}

	(void) madvise(mapaddr, (unsigned int)maplen, (int)vmacc);
	vmaccess = vmacc;

	return (AUDIO_SUCCESS);
}

// Close the file
AudioError AudioFile::
Close()
{
	AudioError	err;

	if (!opened())
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// Rewind the file and rewrite the header with the correct length
	if (GetAccess().Writeable() && (origlen != GetLength())) {

		// sanity check
		if (GetHeader().Time_to_Bytes(GetLength()) !=
		    (lseek(getfd(), (off_t)0, SEEK_END) - hdrsize)) {
			PrintMsg(_MGET_(
			    "AudioFile:Close()...inconsistent length\n"),
			    Fatal);
		}

		// XXX - should be rewritten in C++
		err = (AudioError) audio_rewrite_filesize(getfd(), FILE_AU,
		    (uint_t)GetHeader().Time_to_Bytes(GetLength()), 0, 0);
	}

	// Call the generic file close routine
	err = AudioUnixfile::Close();

	if (mapaddr) {
		munmap(mapaddr, (int)maplen);
		mapaddr = 0;
		maplen = 0;
	}

	// Init important values, in case the file is reopened
	hdrsize = 0;
	seekpos = 0;
	return (RaiseError(err));
}

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

	// If the file is not mapped, call parent ReadData() and return
	if (mapaddr == 0) {
		// Call the real routine
		err = AudioUnixfile::ReadData(buf, len, pos);
		// Update the cached seek pointer
		seekpos += len;
		return (err);
	}

	// If the file is mmapped, do a memcpy() from the mapaddr

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

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

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

	// Make sure we don't read off the end of file
	offset = GetHeader().Time_to_Bytes(pos);

	if ((offset + hdrsize) >= maplen) {
		// trying to read past EOF
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	} else if ((offset + hdrsize + cnt) > maplen) {
		// re-adjust cnt so it reads up to the end of file
		cnt = (size_t)(maplen - (offset + hdrsize));
	}

	// Zero-length reads are finished
	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
		err = AUDIO_SUCCESS;
		err.sys = AUDIO_COPY_ZERO_LIMIT;
		return (err);
	} else {
		cp = mapaddr + offset + hdrsize;
		memcpy((void*)buf, (void*)cp, cnt);
	}

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

	// Check to see if the endian is right. Note that special care
	// doesn't need to be taken because of the mmap, since the data
	// is copied into a separate buffer anyway.
	coerceEndian((unsigned char *)buf, len, localByteOrder());

	return (AUDIO_SUCCESS);
}

// Write data to underlying file from specified buffer.
// No data format translation takes place.
// The object's write position pointer is unaffected.
AudioError AudioFile::
WriteData(
	void*		buf, // source buffer address
	size_t&		len, // buffer length (updated)
	Double&		pos) // start position (updated)
{
	AudioError	err;

	// Call the real routine
	err = AudioUnixfile::WriteData(buf, len, pos);

	// Update the cached seek pointer
	seekpos += len;
	return (err);
}

// Set the Unix file pointer to match a given file position.
AudioError AudioFile::
seekread(
	Double		pos,	// position to seek to
	off_t&		offset)	// returned byte offset
{
	offset = GetHeader().Time_to_Bytes(pos);
	if (offset != seekpos) {
		if (lseek(getfd(), (off_t)(hdrsize + offset), SEEK_SET) < 0)
			return (RaiseError(AUDIO_UNIXERROR, Warning));
		seekpos = offset;
	}
	return (AUDIO_SUCCESS);
}

// Set the Unix file pointer to match a given file position.
// If seek beyond end-of-file, NULL out intervening data.
AudioError AudioFile::
seekwrite(
	Double		pos,	// position to seek to
	off_t&		offset)	// returned byte offset
{
	// If append-only, can't seek backwards into file
	if (GetAccess().Append() && (pos < GetLength()))
		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));

	// If seek beyond eof, fill data
	if (pos > GetLength()) {
		seekwrite(GetLength(), offset);	// seek to eof

		// XXX - not implemented yet

		return (AUDIO_SUCCESS);
	}

	offset = GetHeader().Time_to_Bytes(pos);
	if (offset != seekpos) {
		if (lseek(getfd(), (off_t)(hdrsize + offset), SEEK_SET) < 0)
			return (RaiseError(AUDIO_UNIXERROR, Warning));
		seekpos = offset;
	}
	return (AUDIO_SUCCESS);
}

// Copy routine that handles mapped files
AudioError AudioFile::
AsyncCopy(
	Audio*		to,			// audio object to copy to
	Double&		frompos,
	Double&		topos,
	Double&		limit)
{
	caddr_t		bptr;
	size_t		offset;
	size_t		cnt;
	size_t		svlim;
	Double		svfrom;
	Double		svto;
	AudioHdr	tohdr;
	AudioError	err;

	// If this is NOT mmapped, or the destination is an AudioBuffer,
	// use the default routine
	if ((mapaddr == 0) || to->isBuffer()) {
		return (Audio::AsyncCopy(to, frompos, topos, limit));
	}

	tohdr = to->GetHeader();
	if (err = tohdr.Validate())
		return (err);
	if (limit < 0.)
		return (RaiseError(AUDIO_ERR_BADARG));
	svlim = (size_t)tohdr.Time_to_Bytes(limit);

	// Get maximum possible copy length
	svfrom = GetLength();
	if ((frompos >= svfrom) || ((cnt = (size_t)
	    GetHeader().Time_to_Bytes(svfrom - frompos)) == 0)) {
		limit = 0.;
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	}
	if (!Undefined(limit) && (svlim < cnt))
		cnt = svlim;

	limit = 0.;

	offset = (size_t)GetHeader().Time_to_Bytes(frompos);
	if ((offset + hdrsize) >= maplen) {
		// trying to read past EOF
		err = AUDIO_EOF;
		err.sys = AUDIO_COPY_INPUT_EOF;
		return (err);
	} else if ((offset + hdrsize + cnt) > maplen) {
		// re-adjust cnt so it reads up to the end of file
		cnt = (size_t)(maplen - (offset + hdrsize));
	}

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

	// Write the data to the destination and update pointers/ctrs
	svfrom = frompos;
	svto = topos;
	svlim = cnt;
	bptr = mapaddr + hdrsize + offset;
	err = to->WriteData(bptr, cnt, topos);
	limit = topos - svto;
	frompos = svfrom + limit;

	// Report short writes
	if (!err && (cnt < svlim))
		err.sys = AUDIO_COPY_SHORT_OUTPUT;
	return (err);
}