xref: /illumos-gate/usr/src/cmd/audio/utilities/AudioUnixfile.cc (revision d8048045f75b60d97deaf376b39d437b027becd1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1993-2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <memory.h>
34 #include <unistd.h>
35 
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/file.h>
39 #include <sys/t_lock.h>
40 
41 #include <AudioDebug.h>
42 #include <AudioUnixfile.h>
43 #include <libaudio.h>
44 #include <audio_hdr.h>
45 #include <audio/au.h>
46 
47 // class AudioUnixfile methods
48 
49 // Constructor with pathname and mode arg
50 AudioUnixfile::
AudioUnixfile(const char * path,const FileAccess acc)51 AudioUnixfile(
52 	const char		*path,		// pathname
53 	const FileAccess	acc):		// access mode
54 	AudioStream(path), mode(acc), block(TRUE), fd(-1),
55 	infostring(new char[1]), infolength(1)
56 {
57 	infostring[0] = '\0';
58 }
59 
60 // Destructor
61 AudioUnixfile::
~AudioUnixfile()62 ~AudioUnixfile()
63 {
64 	// If the file is open, close it
65 	if (opened())
66 		(void) Close();
67 
68 	// Deallocate the dynamic storage
69 	delete infostring;
70 }
71 
72 // Generic open with search path routine just calls default Open()
73 AudioError AudioUnixfile::
OpenPath(const char *)74 OpenPath(
75 	const char *)
76 {
77 	return (Open());
78 }
79 
80 // Decode an audio file header
81 // This routine reads the audio file header and decodes it.
82 //
83 // This method should be specialized by subclasses that are not files,
84 // like devices for instance.
85 //
86 // XXX - this routine should be rewritten for C++
87 AudioError AudioUnixfile::
decode_filehdr()88 decode_filehdr()
89 {
90 	Boolean		saveblock;	// saved state of the blocking i/o flag
91 	AudioHdr	hdr_local;	// local copy of header
92 	Audio_hdr	ohdr;		// XXX - old libaudio hdr
93 	au_filehdr_t	fhdr;
94 	char		*ibuf;
95 	int		file_type;
96 	int		infosize;
97 	int		cnt;
98 	struct stat	st;
99 	AudioError	err;
100 
101 	// If fd is not open, or file header already decoded, skip it
102 	if (!isfdset() || opened())
103 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
104 
105 	// Stat the file, to see if it is a regular file
106 	if (fstat(getfd(), &st) < 0)
107 		return (RaiseError(AUDIO_UNIXERROR));
108 
109 	// Make sure the file is not set for blocking i/o
110 	saveblock = GetBlocking();
111 	if (!saveblock)
112 		SetBlocking(TRUE);
113 
114 	// Read the file header, but not the info field
115 	// XXX - Should use C++ input method
116 	cnt = read(getfd(), (char *)&fhdr, sizeof (fhdr));
117 	if (cnt != sizeof (fhdr)) {
118 		return (RaiseError(AUDIO_UNIXERROR));
119 	}
120 
121 	// Check the validity of the header and get the size of the info field
122 	err = (AudioError) audio_decode_filehdr(getfd(), (unsigned char *)&fhdr,
123 	    &file_type, &ohdr, &infosize);
124 	if (err != AUDIO_SUCCESS)
125 		return (RaiseError(err));
126 
127 	// Allocate and read in the info field
128 	ibuf = new char[infosize];
129 	cnt = read(getfd(), ibuf, infosize);
130 	if (cnt != infosize) {
131 		delete[] ibuf;
132 		return (RaiseError(AUDIO_UNIXERROR));
133 	}
134 	SetBlocking(saveblock);		// Restore the saved blocking i/o state
135 
136 	// XXX - convert from libaudio header
137 	hdr_local = GetHeader();
138 	hdr_local.sample_rate = ohdr.sample_rate;
139 	hdr_local.samples_per_unit = ohdr.samples_per_unit;
140 	hdr_local.bytes_per_unit = ohdr.bytes_per_unit;
141 	hdr_local.channels = ohdr.channels;
142 	hdr_local.encoding = (AudioEncoding) ohdr.encoding;
143 	hdr_local.endian = BIG_ENDIAN; // Files are always written in
144 					// big endian.
145 
146 	err = SetHeader(hdr_local);
147 	if (err != AUDIO_SUCCESS) {
148 		delete[] ibuf;
149 		return (RaiseError(err));
150 	}
151 	SetInfostring(ibuf, infosize);
152 	delete[] ibuf;
153 
154 	// Only trust the file size for regular files
155 	if (S_ISREG(st.st_mode)) {
156 		setlength(GetHeader().Bytes_to_Time(
157 		    st.st_size - infosize - sizeof (au_filehdr_t)));
158 
159 		// Sanity check
160 		if ((ohdr.data_size != AUDIO_UNKNOWN_SIZE) &&
161 		    (GetLength() != GetHeader().Bytes_to_Time(ohdr.data_size)))
162 			PrintMsg(_MGET_(
163 			    "AudioUnixfile: header/file size mismatch"));
164 
165 		// always consider it to be unknown if not reading a real file
166 		// since there's no real way to verify if the header is
167 		// correct.
168 	} else {
169 		setlength(AUDIO_UNKNOWN_TIME);
170 	}
171 
172 	// set flag for opened() test
173 	filehdrset = TRUE;
174 
175 	return (AUDIO_SUCCESS);
176 }
177 
178 // Write an audio file header
179 // This routine encodes the audio file header and writes it out.
180 // XXX - It assumes that the file pointer is set to the start of the file.
181 //
182 // This method should be specialized by subclasses that are not files,
183 // like devices for instance.
184 //
185 // XXX - this routine should be rewritten for C++
186 AudioError AudioUnixfile::
encode_filehdr()187 encode_filehdr()
188 {
189 	Boolean		saveblock;	// saved state of the blocking i/o flag
190 	AudioHdr	hdr_local;	// local copy of header
191 	Audio_hdr	ohdr;		// XXX - old libaudio hdr
192 	AudioError	err;
193 
194 	// If fd is not open, or file header already written, skip it
195 	if (!isfdset() || opened())
196 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
197 
198 	// XXX - Set up the libaudio hdr
199 	hdr_local = GetHeader();
200 	hdr_local.endian = BIG_ENDIAN; // Files are always written big endian.
201 	err = SetHeader(hdr_local);
202 	if (err != AUDIO_SUCCESS) {
203 		return (RaiseError(err));
204 	}
205 
206 	ohdr.sample_rate = hdr_local.sample_rate;
207 	ohdr.samples_per_unit = hdr_local.samples_per_unit;
208 	ohdr.bytes_per_unit = hdr_local.bytes_per_unit;
209 	ohdr.channels = hdr_local.channels;
210 	ohdr.encoding = hdr_local.encoding;
211 	if (Undefined(GetLength()))
212 		ohdr.data_size = AUDIO_UNKNOWN_SIZE;
213 	else
214 		ohdr.data_size = (uint_t)GetHeader().Time_to_Bytes(GetLength());
215 
216 	/* Make sure the file is not set for blocking i/o */
217 	saveblock = GetBlocking();
218 	if (!saveblock)
219 		SetBlocking(TRUE);
220 
221 	// XXX - Should use C++ output method
222 	err = (AudioError) audio_write_filehdr(getfd(), &ohdr, FILE_AU,
223 	    infostring, infolength);
224 
225 	// set flag for opened() test
226 	if (err == AUDIO_SUCCESS)
227 		filehdrset = TRUE;
228 
229 	SetBlocking(saveblock);		// Restore the saved blocking i/o state
230 	return (RaiseError(err));
231 }
232 
233 // Set a file blocking/non-blocking
234 // This method should be subclassed by objects that always block (eg, files)
235 void AudioUnixfile::
SetBlocking(Boolean b)236 SetBlocking(
237 	Boolean		b)			// FALSE to set non-blocking
238 {
239 	int		flag;
240 
241 	// If the file is open, set blocking/non-blocking now
242 	if (isfdset()) {
243 		flag = fcntl(getfd(), F_GETFL, 0);
244 		if ((flag < 0) && (errno == EOVERFLOW || errno == EINVAL)) {
245 			RaiseError(AUDIO_UNIXERROR, Fatal,
246 			    (char *)"Large File");
247 		} else if (b) {
248 			flag &= ~(O_NDELAY | O_NONBLOCK);	// set blocking
249 		} else {
250 			flag |= O_NONBLOCK;		// set non-blocking
251 		}
252 		if (fcntl(getfd(), F_SETFL, flag) < 0) {
253 			RaiseError(AUDIO_UNIXERROR, Warning);
254 		}
255 	}
256 	// Set the blocking flag (this may affect the Open() behavior)
257 	block = b;
258 }
259 
260 // Return a pointer to the info string
261 // XXX - returns a pointer to the string stored in the object
262 // XXX - assumes ASCII data
263 char *AudioUnixfile::
GetInfostring(int & len) const264 GetInfostring(
265 	int&		len) const		// returned length of string
266 {
267 	len = infolength;
268 	return (infostring);
269 }
270 
271 // Set the info string
272 void AudioUnixfile::
SetInfostring(const char * str,int len)273 SetInfostring(
274 	const char	*str,			// new info string
275 	int		len)			// length of string
276 {
277 	// If length defaulted, assume an ASCII string
278 	if (len == -1)
279 		len = strlen(str) + 1;
280 	delete infostring;
281 	infostring = new char[len];
282 	infolength = len;
283 	(void) memcpy(infostring, str, len);
284 }
285 
286 // Close file
287 AudioError AudioUnixfile::
Close()288 Close()
289 {
290 	// If the file is open, close it
291 	if (isfdset()) {
292 		if (close(getfd()) < 0)
293 			return (RaiseError(AUDIO_UNIXERROR));
294 	} else {
295 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
296 	}
297 
298 	// Init important values, in case the file is reopened
299 	setfd(-1);
300 	filehdrset = FALSE;
301 	(void) SetReadPosition((Double)0., Absolute);
302 	(void) SetWritePosition((Double)0., Absolute);
303 	return (AUDIO_SUCCESS);
304 }
305 
306 // Read data from underlying file into specified buffer.
307 // No data format translation takes place.
308 // The object's read position is not updated (subclasses can change this)
309 AudioError AudioUnixfile::
ReadData(void * buf,size_t & len,Double & pos)310 ReadData(
311 	void*		buf,		// destination buffer address
312 	size_t&		len,		// buffer length (updated)
313 	Double&		pos)		// start position (updated)
314 {
315 	off_t		offset;
316 	off_t		cnt;
317 	AudioError	err;
318 
319 	// Save buffer size and zero transfer count
320 	cnt = (off_t)len;
321 	len = 0;
322 
323 	// Cannot read if file is not open
324 	if (!opened() || !mode.Readable())
325 		return (RaiseError(AUDIO_ERR_NOEFFECT));
326 
327 	// Position must be valid
328 	if (Undefined(pos) || (pos < 0.) || (cnt < 0))
329 		return (RaiseError(AUDIO_ERR_BADARG));
330 
331 	// Position the file pointer to the right place
332 	err = seekread(pos, offset);
333 	if (err != AUDIO_SUCCESS)
334 		return (err);
335 
336 	// Check for EOF
337 	if (pos >= GetLength()) {
338 		err = AUDIO_EOF;
339 		err.sys = AUDIO_COPY_INPUT_EOF;
340 		return (err);
341 	}
342 
343 	// Zero-length reads are finished
344 	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
345 		err = AUDIO_SUCCESS;
346 		err.sys = AUDIO_COPY_ZERO_LIMIT;
347 		return (err);
348 	}
349 
350 	// Read as much data as possible
351 	cnt = read(fd, (char *)buf, (int)cnt);
352 	if (cnt < 0) {
353 		if (errno == EOVERFLOW) {
354 			perror("read");
355 			exit(1);
356 		} else if ((errno == EINTR) ||
357 		    (((errno == EWOULDBLOCK) || (errno == EAGAIN)) &&
358 		    !GetBlocking())) {
359 		// Is this an interrupted or failed non-blocking request?
360 			err = AUDIO_SUCCESS;
361 			err.sys = AUDIO_COPY_SHORT_INPUT;
362 			return (err);
363 		}
364 		return (RaiseError(AUDIO_UNIXERROR));
365 	}
366 
367 	// End-of-file?
368 	if ((cnt == 0) && GetBlocking()) {
369 		if (isDevice() || isPipe()) {
370 			AUDIO_DEBUG((1,
371 			    "Zero-length blocking device/pipe read?!\n"));
372 		}
373 		err = AUDIO_EOF;
374 		err.sys = AUDIO_COPY_INPUT_EOF;
375 		return (err);
376 	}
377 	err = AUDIO_SUCCESS;
378 	if (cnt == 0) {
379 		err.sys = AUDIO_COPY_SHORT_INPUT;
380 	}
381 
382 	// Return the updated byte count and position
383 	len = (size_t)cnt;
384 	if (GetHeader().Bytes_to_Bytes(cnt) != len) {
385 		AUDIO_DEBUG((1,
386 		    "Read returned a partial sample frame?!\n"));
387 	}
388 	pos = GetHeader().Bytes_to_Time(offset + len);
389 
390 	// Check to see if the endian is right.
391 	coerceEndian((unsigned char *)buf, len, localByteOrder());
392 
393 	return (err);
394 }
395 
396 // Write data to underlying file from specified buffer.
397 // No data format translation takes place.
398 // The object's write position is not updated (subclasses can change this)
399 AudioError AudioUnixfile::
WriteData(void * buf,size_t & len,Double & pos)400 WriteData(
401 	void*		buf,		// source buffer address
402 	size_t&		len,		// buffer length (updated)
403 	Double&		pos)		// start position (updated)
404 {
405 	off_t		offset;
406 	off_t		cnt;
407 	AudioError	err;
408 
409 	// Save buffer size and zero transfer count
410 	cnt = (off_t)len;
411 	len = 0;
412 
413 	// Cannot write if file is not open
414 	if (!opened() || !mode.Writeable())
415 		return (RaiseError(AUDIO_ERR_NOEFFECT));
416 
417 	// Position must be valid
418 	if (Undefined(pos) || (pos < 0.) || (cnt < 0))
419 		return (RaiseError(AUDIO_ERR_BADARG));
420 
421 	// Zero-length writes are easy
422 	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
423 		err = AUDIO_SUCCESS;
424 		err.sys = AUDIO_COPY_ZERO_LIMIT;
425 		return (err);
426 	}
427 
428 	// Position the file pointer to the right place
429 	err = seekwrite(pos, offset);
430 	if (err != AUDIO_SUCCESS)
431 		return (err);
432 
433 	// Make sure data is in target's endian format before writing.
434 	// This conversion is done inplace so we need to change back.
435 	// We assume that the data in buf is in localByteOrder.
436 	// Only files should have order issues.
437 	if (localByteOrder() != GetHeader().endian)
438 		coerceEndian((unsigned char *)buf, (size_t)cnt, SWITCH_ENDIAN);
439 
440 	// Write as much data as possible
441 	err = AUDIO_SUCCESS;
442 	cnt = write(fd, (char *)buf, (int)cnt);
443 	if (cnt < 0) {
444 		if (errno == EFBIG) {
445 			perror("write");
446 			exit(1);
447 		} else if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
448 			// Is this a failed non-blocking request?
449 			err.sys = AUDIO_COPY_SHORT_OUTPUT;
450 			return (err);
451 		}
452 		return (RaiseError(AUDIO_UNIXERROR));
453 	}
454 	if (cnt == 0)
455 		err.sys = AUDIO_COPY_SHORT_OUTPUT;
456 
457 	// Switch the endian back if local order doesn't match target order.
458 	if (localByteOrder() != GetHeader().endian)
459 		coerceEndian((unsigned char *)buf, (size_t)cnt, SWITCH_ENDIAN);
460 
461 	// Return the updated byte count and position
462 	len = (size_t)cnt;
463 	pos = GetHeader().Bytes_to_Time(offset + len);
464 
465 	// If the current position is beyond old EOF, update the size
466 	if (!Undefined(GetLength()) && (pos > GetLength())) {
467 		setlength(pos);
468 	}
469 
470 	return (AUDIO_SUCCESS);
471 }
472 
473 // Seek in input stream
474 // Ordinary streams (ie, pipes and devices) cannot be rewound.
475 // A forward seek in them consumes data by reading it.
476 //
477 // This method should be specialized by subclasses that can actually seek,
478 // like regular files for instance.
479 //
480 AudioError AudioUnixfile::
seekread(Double pos,off_t & offset)481 seekread(
482 	Double		pos,		// position to seek to
483 	off_t&		offset)		// returned byte offset
484 {
485 	char		*bufp;		// temporary input buffer
486 	size_t		bufl;		// input buffer size
487 	size_t		cnt;		// input byte count
488 	long		icnt;		// read size
489 	Boolean		saveblock;	// saved state of the blocking i/o flag
490 	Double		buflen;
491 	AudioError	err;
492 
493 	offset = GetHeader().Time_to_Bytes(pos);
494 	pos -= ReadPosition();
495 
496 	// If the seek is backwards, do nothing
497 	if (pos < 0.)
498 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
499 
500 	// If the seek is to the current position, then do nothing.
501 	icnt = GetHeader().Time_to_Bytes(pos);
502 	if (icnt == 0)
503 		return (AUDIO_SUCCESS);
504 
505 	// The seek is determinate and forward.
506 	// We'll have to consume data to get there.
507 	// First allocate a buffer to stuff the data into.
508 	// Then set the stream for blocking i/o (saving the old state).
509 	buflen = max(pos, 1.);
510 	bufl = (size_t)GetHeader().Time_to_Bytes(buflen);
511 	bufp = new char[bufl];
512 	if (bufp == 0) {		// allocation error, try a smaller buf
513 		bufl = (size_t)sysconf(_SC_PAGESIZE);
514 		bufp = new char[bufl];
515 		if (bufp == 0)
516 			return (RaiseError(AUDIO_UNIXERROR));
517 	}
518 	// XXX - May have to realign to partial frame count!
519 
520 	saveblock = GetBlocking();
521 	if (!saveblock)
522 		SetBlocking(TRUE);
523 
524 	// Loop until the seek is satisfied (or an error occurs).
525 	do {
526 		// Limit the read to keep from going too far
527 		cnt = (icnt >= (long)bufl) ? bufl : (size_t)icnt;
528 		err = Read(bufp, cnt);
529 		if (err != AUDIO_SUCCESS)
530 			break;
531 		icnt -= (long)cnt;
532 	} while (icnt > 0);
533 
534 	SetBlocking(saveblock);		// Restore the saved blocking i/o state
535 	delete[] bufp;			// Free the temporary buffer
536 	return (RaiseError(err));
537 }
538 
539 // Seek in output stream
540 // Ordinary streams (ie, pipes and devices) cannot be rewound.
541 // A forward seek in them writes NULL data.
542 //
543 // This method should be specialized by subclasses that can actually seek,
544 // like regular files for instance.
545 //
546 AudioError AudioUnixfile::
seekwrite(Double pos,off_t & offset)547 seekwrite(
548 	Double		pos,		// position to seek to
549 	off_t&		offset)		// returned byte offset
550 {
551 	char		*bufp;		// temporary output buffer
552 	size_t		bufl;		// output buffer size
553 	size_t		cnt;		// output byte count
554 	long		ocnt;		// write size
555 	Boolean		saveblock;	// saved state of the blocking i/o flag
556 	Double		buflen;
557 	AudioError	err;
558 
559 	offset = GetHeader().Time_to_Bytes(pos);
560 	pos -= WritePosition();
561 
562 	// If the seek is backwards, do nothing
563 	if (pos < 0.)
564 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
565 
566 	// If the seek is to the current position, then do nothing.
567 	ocnt = GetHeader().Time_to_Bytes(pos);
568 	if (ocnt == 0)
569 		return (AUDIO_SUCCESS);
570 
571 	// The seek is determinate and forward.
572 	// We'll have to produce NULL data to get there.
573 	// XXX - not implemented correctly yet
574 	buflen = max(pos, 1.);
575 	bufl = (size_t)GetHeader().Time_to_Bytes(buflen);
576 	bufp = new char[bufl];
577 	if (bufp == 0) {		// allocation error, try a smaller buf
578 		bufl = (size_t)sysconf(_SC_PAGESIZE);
579 		bufp = new char[bufl];
580 		if (bufp == 0)
581 			return (RaiseError(AUDIO_UNIXERROR));
582 	}
583 
584 	// XXX - May have to realign to partial frame count!
585 	saveblock = GetBlocking();
586 	if (!saveblock)
587 		SetBlocking(TRUE);
588 
589 	// Loop until the seek is satisfied (or an error occurs).
590 	do {
591 		// Limit the write to keep from going too far
592 		cnt = (ocnt >= (long)bufl) ? bufl : (size_t)ocnt;
593 		err = Write(bufp, cnt);
594 		if (err != AUDIO_SUCCESS)
595 			break;
596 		ocnt -= (long)cnt;
597 	} while (ocnt > 0);
598 
599 	SetBlocking(saveblock);		// Restore the saved blocking i/o state
600 	delete[] bufp;			// Free the temporary buffer
601 	return (RaiseError(err));
602 }
603