xref: /illumos-gate/usr/src/cmd/audio/utilities/AudioFile.cc (revision dd72704bd9e794056c558153663c739e2012d721)
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 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <malloc.h>
32 #include <string.h>
33 #include <fcntl.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <sys/mman.h>
37 
38 #include <AudioFile.h>
39 #include <AudioLib.h>
40 #include <AudioDebug.h>
41 #include <libaudio.h>
42 #include <audio_hdr.h>
43 
44 // class AudioFile methods
45 
46 
47 // Initialize temporary file params
48 #define	TMPDIR		"/tmp"
49 #define	TMPFILE		"/audiotoolXXXXXX"
50 static char		*tmpdir = NULL;
51 static const char	*tmpname = "(temporary file)";
52 static const FileAccess	tmpmode = ReadWrite;
53 static const VMAccess	defaccess = SequentialAccess;
54 
55 // Initialize default access mode, used when a filename is supplied
56 const FileAccess	AudioFile::defmode = ReadOnly;
57 
58 // Default audio file path prefix environment variable
59 const char *AudioFile::AUDIO_PATH = "AUDIOPATH";
60 
61 
62 // Constructor with no arguments opens a read/write temporary file
63 AudioFile::
64 AudioFile():
65 	AudioUnixfile(tmpname, tmpmode),
66 	hdrsize(0), seekpos(0), origlen(0.), mapaddr(0), maplen(0),
67 	vmaccess(defaccess)
68 {
69 }
70 
71 // Constructor with pathname and optional mode arg
72 AudioFile::
73 AudioFile(
74 	const char		*path,		// filename
75 	const FileAccess	acc):		// access mode
76 	AudioUnixfile(path, acc),
77 	hdrsize(0), seekpos(0), origlen(0.), mapaddr(0), maplen(0),
78 	vmaccess(defaccess)
79 {
80 }
81 
82 // Destructor must call the local Close() routine
83 AudioFile::
84 ~AudioFile()
85 {
86 	// If the file was open, close it
87 	if (opened())
88 		(void) Close();
89 }
90 
91 // Set a default temporary file directory
92 AudioError AudioFile::
93 SetTempPath(
94 	const char	*path)
95 {
96 	struct stat	st;
97 
98 	// Verify intended path
99 	if ((stat(path, &st) < 0) ||
100 	    !S_ISDIR(st.st_mode) ||
101 	    (access(path, W_OK) < 0)) {
102 		errno = ENOTDIR;
103 		return (AUDIO_UNIXERROR);
104 	}
105 
106 	if (tmpdir != NULL)
107 		(void) free(tmpdir);
108 	tmpdir = (char *)malloc(strlen(path) + 1);
109 	(void) strcpy(tmpdir, path);
110 	return (AUDIO_SUCCESS);
111 }
112 
113 
114 // Create a named file according to the current mode setting
115 AudioError AudioFile::
116 createfile(
117 	const char	*path)			// pathname or 0
118 {
119 	char		*tmpf;
120 	char		*tmpstr;
121 	int		openmode;
122 	int		desc;
123 	AudioError	err;
124 
125 	// Convert the open mode to an int argument for open()
126 	openmode = GetAccess();
127 
128 	// Was the header properly set?
129 	if (!hdrset())
130 		return (RaiseError(AUDIO_ERR_BADHDR));
131 
132 	// Can't create if already opened or if mode or name not set
133 	if ((openmode == -1) || opened() || (strlen(path) == 0))
134 		return (RaiseError(AUDIO_ERR_NOEFFECT));
135 
136 	// If temporary file, create and unlink it.
137 	if (strcmp(path, tmpname) == 0) {
138 		// Construct the temporary file path
139 		tmpstr = (char *)malloc(1 + strlen(TMPFILE) +
140 		    strlen((tmpdir == NULL) ? TMPDIR : tmpdir));
141 		(void) sprintf(tmpstr, "%s%s",
142 		    (tmpdir == NULL) ? TMPDIR : tmpdir, TMPFILE);
143 		tmpf = mktemp(tmpstr);
144 
145 		// Open the temp file and unlink it
146 		err = createfile(tmpf);
147 		if ((err == AUDIO_SUCCESS) && (unlink(tmpf) < 0)) {
148 			(void) Close();
149 			err = RaiseError(AUDIO_UNIXERROR, Warning);
150 		}
151 		(void) free(tmpstr);
152 		return (err);
153 	}
154 
155 	// Create the file
156 	desc = open(path, openmode | O_CREAT | O_TRUNC, 0666);
157 	if ((desc < 0) && (errno == EOVERFLOW)) {
158 		return (RaiseError(AUDIO_UNIXERROR, Fatal,
159 		    (char *)"Large File"));
160 	} else if (desc < 0) {
161 		return (RaiseError(AUDIO_UNIXERROR));
162 	}
163 
164 	// Set the file descriptor (this marks the file open)
165 	setfd(desc);
166 
167 	// Write the file header with current (usually unknown) size
168 	err = encode_filehdr();
169 	if (err != AUDIO_SUCCESS) {
170 		setfd(-1);
171 		(void) close(desc);		// If error, remove file
172 		(void) unlink(path);
173 		return (err);
174 	}
175 
176 	// Save the length that got written, then set it to zero
177 	origlen = GetLength();
178 	setlength(0.);
179 
180 	// Set the size of the file header
181 	hdrsize = lseek(desc, (off_t)0, SEEK_CUR);
182 	if (hdrsize < 0) {
183 		setfd(-1);
184 		(void) close(desc);		// If error, remove file
185 		(void) unlink(path);
186 		return (err);
187 	}
188 	seekpos = 0;
189 
190 	return (AUDIO_SUCCESS);
191 }
192 
193 // Create a file whose name is already set, according to the mode setting
194 AudioError AudioFile::
195 Create()
196 {
197 	return (createfile(GetName()));
198 }
199 
200 // Open a file whose name is set
201 AudioError AudioFile::
202 Open()
203 {
204 	return (OpenPath(NULL));
205 }
206 
207 // Open a file, using the specified path prefixes
208 AudioError AudioFile::
209 OpenPath(
210 	const char	*path)
211 {
212 	char		*filename;
213 	int		flen;
214 	char		*prefix;
215 	char		*str;
216 	char		*wrk;
217 	char		*pathname;
218 	int		openmode;
219 	AudioError	err;
220 
221 	// Convert the open mode to an int argument for open()
222 	openmode = GetAccess();
223 	filename = GetName();
224 	flen = strlen(filename);
225 
226 	// Can't open if already opened or if mode or name not set
227 	if ((openmode == -1) || opened() || (strlen(filename) == 0))
228 		return (RaiseError(AUDIO_ERR_NOEFFECT));
229 
230 	// Search path:
231 	//	1) try name: if not found and not readonly:
232 	//		if Append mode, try creating it
233 	//	2) if name is a relative pathname, and 'path' is not NULL:
234 	//		try every path prefix in 'path'
235 
236 	err = tryopen(filename, openmode);
237 	if (!err)
238 		return (AUDIO_SUCCESS);
239 	if (GetAccess().Writeable() || (filename[0] == '/')) {
240 		// If file is non-existent and Append mode, try creating it.
241 		if ((err == AUDIO_UNIXERROR) && (err.sys == ENOENT) &&
242 		    GetAccess().Append() && hdrset()) {
243 			return (Create());
244 		}
245 		return (RaiseError(err));
246 	}
247 
248 	// Try path as an environment variable name, else assume it is a path
249 	str = (path == NULL) ? NULL : getenv(path);
250 	if (str == NULL)
251 		str = (char *)path;
252 
253 	if (str != NULL) {
254 		// Make a copy of the path, to parse it
255 		wrk = new char[strlen(str) + 1];
256 		(void) strcpy(wrk, str);
257 		str = wrk;
258 
259 		// Try each component as a path prefix
260 		for (prefix = str;
261 		    (prefix != NULL) && (prefix[0] != '\0');
262 		    prefix = str) {
263 			str = strchr(str, ':');
264 			if (str != NULL)
265 				*str++ = '\0';
266 			pathname = new char[strlen(prefix) + flen + 2];
267 			(void) sprintf(pathname, "%s/%s", prefix, filename);
268 			err = tryopen(pathname, openmode);
269 			delete pathname;
270 			switch (err) {
271 			case AUDIO_SUCCESS:	// found the file
272 				delete wrk;
273 				return (RaiseError(err));
274 			// XXX - if file found but not audio, stop looking??
275 			}
276 		}
277 		delete wrk;
278 	}
279 	// Can't find file.  Return the original error condition.
280 	return (RaiseError(tryopen(filename, openmode)));
281 }
282 
283 // Attempt to open the given audio file
284 AudioError AudioFile::
285 tryopen(
286 	const char	*pathname,
287 	int		openmode)
288 {
289 	struct stat	st;
290 	int		desc;
291 	AudioError	err;
292 
293 	// If the name is changing, set the new one
294 	if (pathname != GetName())
295 		SetName(pathname);
296 
297 	// Does the file exist?
298 	if (stat(pathname, &st) < 0)
299 		return (AUDIO_UNIXERROR);
300 
301 	// If not a regular file, stop right there
302 	if (!S_ISREG(st.st_mode))
303 		return (AUDIO_ERR_BADFILEHDR);
304 
305 	// Open the file and check that it's an audio file
306 	desc = open(GetName(), openmode);
307 	if ((desc < 0) && (errno == EOVERFLOW)) {
308 		return (RaiseError(AUDIO_UNIXERROR, Fatal,
309 		    (char *)"Large File"));
310 	} else if (desc < 0) {
311 		return (AUDIO_UNIXERROR);
312 	}
313 
314 	// Set the file descriptor (this marks the file open)
315 	setfd(desc);
316 
317 	err = decode_filehdr();
318 	if (err != AUDIO_SUCCESS) {
319 		(void) close(desc);
320 		setfd(-1);
321 		return (err);
322 	}
323 
324 	// Save the length of the data and the size of the file header
325 	origlen = GetLength();
326 	hdrsize = (off_t)lseek(desc, (off_t)0, SEEK_CUR);
327 	if (hdrsize < 0) {
328 		(void) close(desc);
329 		setfd(-1);
330 		return (err);
331 	}
332 	seekpos = 0;
333 
334 	// If this is ReadOnly file, mmap() it.  Don't worry if mmap() fails.
335 	if (!GetAccess().Writeable()) {
336 		maplen = st.st_size;
337 
338 		/*
339 		 * Can't mmap LITTLE_ENDIAN as they are converted in
340 		 * place.
341 		 */
342 		if (localByteOrder() == BIG_ENDIAN) {
343 			if ((mapaddr = (caddr_t)mmap(0, (int)maplen, PROT_READ,
344 				MAP_SHARED, desc, 0)) != (caddr_t)-1) {
345 				// set default access method
346 				(void) madvise(mapaddr, (unsigned int)maplen,
347 				    (int)GetAccessType());
348 			} else {
349 				(void) RaiseError(AUDIO_UNIXERROR, Warning,
350 				    (char *)"Could not mmap() file");
351 				mapaddr = 0;
352 				maplen = 0;
353 			}
354 		} else {
355 			mapaddr = 0;
356 			maplen = 0;
357 		}
358 	}
359 	return (AUDIO_SUCCESS);
360 }
361 
362 // set VM access hint for mmapped files
363 AudioError AudioFile::
364 SetAccessType(VMAccess vmacc)
365 {
366 	if (!opened()) {
367 		return (AUDIO_ERR_NOEFFECT);
368 	}
369 
370 	if (mapaddr == 0) {
371 		return (AUDIO_ERR_NOEFFECT);
372 	}
373 
374 	(void) madvise(mapaddr, (unsigned int)maplen, (int)vmacc);
375 	vmaccess = vmacc;
376 
377 	return (AUDIO_SUCCESS);
378 }
379 
380 // Close the file
381 AudioError AudioFile::
382 Close()
383 {
384 	AudioError	err;
385 
386 	if (!opened())
387 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
388 
389 	// Rewind the file and rewrite the header with the correct length
390 	if (GetAccess().Writeable() && (origlen != GetLength())) {
391 
392 		// sanity check
393 		if (GetHeader().Time_to_Bytes(GetLength()) !=
394 		    (lseek(getfd(), (off_t)0, SEEK_END) - hdrsize)) {
395 			PrintMsg(_MGET_(
396 			    "AudioFile:Close()...inconsistent length\n"),
397 			    Fatal);
398 		}
399 
400 		// XXX - should be rewritten in C++
401 		err = (AudioError) audio_rewrite_filesize(getfd(), FILE_AU,
402 		    (uint_t)GetHeader().Time_to_Bytes(GetLength()), 0, 0);
403 	}
404 
405 	// Call the generic file close routine
406 	err = AudioUnixfile::Close();
407 
408 	if (mapaddr) {
409 		munmap(mapaddr, (int)maplen);
410 		mapaddr = 0;
411 		maplen = 0;
412 	}
413 
414 	// Init important values, in case the file is reopened
415 	hdrsize = 0;
416 	seekpos = 0;
417 	return (RaiseError(err));
418 }
419 
420 // Read data from underlying file into specified buffer.
421 // No data format translation takes place.
422 // The object's read position pointer is unaffected.
423 AudioError AudioFile::
424 ReadData(
425 	void*		buf,		// destination buffer address
426 	size_t&		len,		// buffer length (updated)
427 	Double&		pos)		// start position (updated)
428 {
429 	off_t		offset;
430 	size_t		cnt;
431 	caddr_t		cp;
432 	AudioError	err;
433 
434 	// If the file is not mapped, call parent ReadData() and return
435 	if (mapaddr == 0) {
436 		// Call the real routine
437 		err = AudioUnixfile::ReadData(buf, len, pos);
438 		// Update the cached seek pointer
439 		seekpos += len;
440 		return (err);
441 	}
442 
443 	// If the file is mmapped, do a memcpy() from the mapaddr
444 
445 	// Save buffer size and zero transfer count
446 	cnt = (size_t)len;
447 	len = 0;
448 
449 	// Cannot read if file is not open
450 	if (!opened() || !GetAccess().Readable())
451 		return (RaiseError(AUDIO_ERR_NOEFFECT));
452 
453 	// Position must be valid
454 	if (Undefined(pos) || (pos < 0.) || ((int)cnt < 0))
455 		return (RaiseError(AUDIO_ERR_BADARG));
456 
457 	// Make sure we don't read off the end of file
458 	offset = GetHeader().Time_to_Bytes(pos);
459 
460 	if ((offset + hdrsize) >= maplen) {
461 		// trying to read past EOF
462 		err = AUDIO_EOF;
463 		err.sys = AUDIO_COPY_INPUT_EOF;
464 		return (err);
465 	} else if ((offset + hdrsize + cnt) > maplen) {
466 		// re-adjust cnt so it reads up to the end of file
467 		cnt = (size_t)(maplen - (offset + hdrsize));
468 	}
469 
470 	// Zero-length reads are finished
471 	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
472 		err = AUDIO_SUCCESS;
473 		err.sys = AUDIO_COPY_ZERO_LIMIT;
474 		return (err);
475 	} else {
476 		cp = mapaddr + offset + hdrsize;
477 		memcpy((void*)buf, (void*)cp, cnt);
478 	}
479 
480 	// Return the updated byte count and position
481 	len = cnt;
482 	pos = GetHeader().Bytes_to_Time(offset + len);
483 
484 	// Check to see if the endian is right. Note that special care
485 	// doesn't need to be taken because of the mmap, since the data
486 	// is copied into a separate buffer anyway.
487 	coerceEndian((unsigned char *)buf, len, localByteOrder());
488 
489 	return (AUDIO_SUCCESS);
490 }
491 
492 // Write data to underlying file from specified buffer.
493 // No data format translation takes place.
494 // The object's write position pointer is unaffected.
495 AudioError AudioFile::
496 WriteData(
497 	void*		buf, // source buffer address
498 	size_t&		len, // buffer length (updated)
499 	Double&		pos) // start position (updated)
500 {
501 	AudioError	err;
502 
503 	// Call the real routine
504 	err = AudioUnixfile::WriteData(buf, len, pos);
505 
506 	// Update the cached seek pointer
507 	seekpos += len;
508 	return (err);
509 }
510 
511 // Set the Unix file pointer to match a given file position.
512 AudioError AudioFile::
513 seekread(
514 	Double		pos,	// position to seek to
515 	off_t&		offset)	// returned byte offset
516 {
517 	offset = GetHeader().Time_to_Bytes(pos);
518 	if (offset != seekpos) {
519 		if (lseek(getfd(), (off_t)(hdrsize + offset), SEEK_SET) < 0)
520 			return (RaiseError(AUDIO_UNIXERROR, Warning));
521 		seekpos = offset;
522 	}
523 	return (AUDIO_SUCCESS);
524 }
525 
526 // Set the Unix file pointer to match a given file position.
527 // If seek beyond end-of-file, NULL out intervening data.
528 AudioError AudioFile::
529 seekwrite(
530 	Double		pos,	// position to seek to
531 	off_t&		offset)	// returned byte offset
532 {
533 	// If append-only, can't seek backwards into file
534 	if (GetAccess().Append() && (pos < GetLength()))
535 		return (RaiseError(AUDIO_ERR_NOEFFECT, Warning));
536 
537 	// If seek beyond eof, fill data
538 	if (pos > GetLength()) {
539 		seekwrite(GetLength(), offset);	// seek to eof
540 
541 		// XXX - not implemented yet
542 
543 		return (AUDIO_SUCCESS);
544 	}
545 
546 	offset = GetHeader().Time_to_Bytes(pos);
547 	if (offset != seekpos) {
548 		if (lseek(getfd(), (off_t)(hdrsize + offset), SEEK_SET) < 0)
549 			return (RaiseError(AUDIO_UNIXERROR, Warning));
550 		seekpos = offset;
551 	}
552 	return (AUDIO_SUCCESS);
553 }
554 
555 // Copy routine that handles mapped files
556 AudioError AudioFile::
557 AsyncCopy(
558 	Audio*		to,			// audio object to copy to
559 	Double&		frompos,
560 	Double&		topos,
561 	Double&		limit)
562 {
563 	caddr_t		bptr;
564 	size_t		offset;
565 	size_t		cnt;
566 	size_t		svlim;
567 	Double		svfrom;
568 	Double		svto;
569 	AudioHdr	tohdr;
570 	AudioError	err;
571 
572 	// If this is NOT mmapped, or the destination is an AudioBuffer,
573 	// use the default routine
574 	if ((mapaddr == 0) || to->isBuffer()) {
575 		return (Audio::AsyncCopy(to, frompos, topos, limit));
576 	}
577 
578 	tohdr = to->GetHeader();
579 	if (err = tohdr.Validate())
580 		return (err);
581 	if (limit < 0.)
582 		return (RaiseError(AUDIO_ERR_BADARG));
583 	svlim = (size_t)tohdr.Time_to_Bytes(limit);
584 
585 	// Get maximum possible copy length
586 	svfrom = GetLength();
587 	if ((frompos >= svfrom) || ((cnt = (size_t)
588 	    GetHeader().Time_to_Bytes(svfrom - frompos)) == 0)) {
589 		limit = 0.;
590 		err = AUDIO_EOF;
591 		err.sys = AUDIO_COPY_INPUT_EOF;
592 		return (err);
593 	}
594 	if (!Undefined(limit) && (svlim < cnt))
595 		cnt = svlim;
596 
597 	limit = 0.;
598 
599 	offset = (size_t)GetHeader().Time_to_Bytes(frompos);
600 	if ((offset + hdrsize) >= maplen) {
601 		// trying to read past EOF
602 		err = AUDIO_EOF;
603 		err.sys = AUDIO_COPY_INPUT_EOF;
604 		return (err);
605 	} else if ((offset + hdrsize + cnt) > maplen) {
606 		// re-adjust cnt so it reads up to the end of file
607 		cnt = (size_t)(maplen - (offset + hdrsize));
608 	}
609 
610 	// Zero-length reads are done
611 	if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
612 		err = AUDIO_SUCCESS;
613 		err.sys = AUDIO_COPY_ZERO_LIMIT;
614 		return (err);
615 	}
616 
617 	// Write the data to the destination and update pointers/ctrs
618 	svfrom = frompos;
619 	svto = topos;
620 	svlim = cnt;
621 	bptr = mapaddr + hdrsize + offset;
622 	err = to->WriteData(bptr, cnt, topos);
623 	limit = topos - svto;
624 	frompos = svfrom + limit;
625 
626 	// Report short writes
627 	if (!err && (cnt < svlim))
628 		err.sys = AUDIO_COPY_SHORT_OUTPUT;
629 	return (err);
630 }
631