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::
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::
AudioFile(const char * path,const FileAccess acc)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::
~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::
SetTempPath(const char * path)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::
createfile(const char * path)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::
Create()195 Create()
196 {
197 return (createfile(GetName()));
198 }
199
200 // Open a file whose name is set
201 AudioError AudioFile::
Open()202 Open()
203 {
204 return (OpenPath(NULL));
205 }
206
207 // Open a file, using the specified path prefixes
208 AudioError AudioFile::
OpenPath(const char * path)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::
tryopen(const char * pathname,int openmode)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::
SetAccessType(VMAccess vmacc)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::
Close()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::
ReadData(void * buf,size_t & len,Double & pos)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::
WriteData(void * buf,size_t & len,Double & pos)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::
seekread(Double pos,off_t & offset)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::
seekwrite(Double pos,off_t & offset)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::
AsyncCopy(Audio * to,Double & frompos,Double & topos,Double & limit)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 err = tohdr.Validate();
580 if (err != AUDIO_SUCCESS)
581 return (err);
582 if (limit < 0.)
583 return (RaiseError(AUDIO_ERR_BADARG));
584 svlim = (size_t)tohdr.Time_to_Bytes(limit);
585
586 // Get maximum possible copy length
587 svfrom = GetLength();
588 if ((frompos >= svfrom) || ((cnt = (size_t)
589 GetHeader().Time_to_Bytes(svfrom - frompos)) == 0)) {
590 limit = 0.;
591 err = AUDIO_EOF;
592 err.sys = AUDIO_COPY_INPUT_EOF;
593 return (err);
594 }
595 if (!Undefined(limit) && (svlim < cnt))
596 cnt = svlim;
597
598 limit = 0.;
599
600 offset = (size_t)GetHeader().Time_to_Bytes(frompos);
601 if ((offset + hdrsize) >= maplen) {
602 // trying to read past EOF
603 err = AUDIO_EOF;
604 err.sys = AUDIO_COPY_INPUT_EOF;
605 return (err);
606 } else if ((offset + hdrsize + cnt) > maplen) {
607 // re-adjust cnt so it reads up to the end of file
608 cnt = (size_t)(maplen - (offset + hdrsize));
609 }
610
611 // Zero-length reads are done
612 if (GetHeader().Bytes_to_Bytes(cnt) == 0) {
613 err = AUDIO_SUCCESS;
614 err.sys = AUDIO_COPY_ZERO_LIMIT;
615 return (err);
616 }
617
618 // Write the data to the destination and update pointers/ctrs
619 svfrom = frompos;
620 svto = topos;
621 svlim = cnt;
622 bptr = mapaddr + hdrsize + offset;
623 err = to->WriteData(bptr, cnt, topos);
624 limit = topos - svto;
625 frompos = svfrom + limit;
626
627 // Report short writes
628 if (!err && (cnt < svlim))
629 err.sys = AUDIO_COPY_SHORT_OUTPUT;
630 return (err);
631 }
632