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