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::
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::
AudioFile(const char * path,const FileAccess acc)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::
~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::
SetTempPath(const char * path)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::
createfile(const char * path)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::
Create()200 Create()
201 {
202 return (createfile(GetName()));
203 }
204
205 // Open a file whose name is set
206 AudioError AudioFile::
Open()207 Open()
208 {
209 return (OpenPath(NULL));
210 }
211
212 // Open a file, using the specified path prefixes
213 AudioError AudioFile::
OpenPath(const char * path)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::
tryopen(const char * pathname,int openmode)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::
SetAccessType(VMAccess vmacc)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::
Close()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::
ReadData(void * buf,size_t & len,Double & pos)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::
WriteData(void * buf,size_t & len,Double & pos)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::
seekread(Double pos,off_t & offset)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::
seekwrite(Double pos,off_t & offset)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::
AsyncCopy(Audio * to,Double & frompos,Double & topos,Double & limit)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