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