1/* 2 * libid3tag - ID3 tag manipulation library 3 * Copyright (C) 2000-2004 Underbit Technologies, Inc. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 * 19 * $Id: file.c,v 1.21 2004/01/23 09:41:32 rob Exp $ 20 */ 21 22# ifdef HAVE_CONFIG_H 23# include "config.h" 24# endif 25 26# include "global.h" 27 28# include <stdio.h> 29# include <stdlib.h> 30# include <string.h> 31 32# ifdef HAVE_UNISTD_H 33# include <unistd.h> 34# endif 35 36# ifdef HAVE_ASSERT_H 37# include <assert.h> 38# endif 39 40# include "id3tag.h" 41# include "file.h" 42# include "tag.h" 43# include "field.h" 44 45struct filetag { 46 struct id3_tag *tag; 47 unsigned long location; 48 id3_length_t length; 49}; 50 51struct id3_file { 52 FILE *iofile; 53 enum id3_file_mode mode; 54 char *path; 55 56 int flags; 57 58 struct id3_tag *primary; 59 60 unsigned int ntags; 61 struct filetag *tags; 62}; 63 64enum { 65 ID3_FILE_FLAG_ID3V1 = 0x0001 66}; 67 68/* 69 * NAME: query_tag() 70 * DESCRIPTION: check for a tag at a file's current position 71 */ 72static 73signed long query_tag(FILE *iofile) 74{ 75 fpos_t save_position; 76 id3_byte_t query[ID3_TAG_QUERYSIZE]; 77 signed long size; 78 79 if (fgetpos(iofile, &save_position) == -1) 80 return 0; 81 82 size = id3_tag_query(query, fread(query, 1, sizeof(query), iofile)); 83 84 if (fsetpos(iofile, &save_position) == -1) 85 return 0; 86 87 return size; 88} 89 90/* 91 * NAME: read_tag() 92 * DESCRIPTION: read and parse a tag at a file's current position 93 */ 94static 95struct id3_tag *read_tag(FILE *iofile, id3_length_t size) 96{ 97 id3_byte_t *data; 98 struct id3_tag *tag = 0; 99 100 data = malloc(size); 101 if (data) { 102 if (fread(data, size, 1, iofile) == 1) 103 tag = id3_tag_parse(data, size); 104 105 free(data); 106 } 107 108 return tag; 109} 110 111/* 112 * NAME: update_primary() 113 * DESCRIPTION: update the primary tag with data from a new tag 114 */ 115static 116int update_primary(struct id3_tag *tag, struct id3_tag const *new) 117{ 118 unsigned int i; 119 struct id3_frame *frame; 120 121 if (new) { 122 if (!(new->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)) 123 id3_tag_clearframes(tag); 124 125 i = 0; 126 while ((frame = id3_tag_findframe(new, 0, i++))) { 127 if (id3_tag_attachframe(tag, frame) == -1) 128 return -1; 129 } 130 } 131 132 return 0; 133} 134 135/* 136 * NAME: tag_compare() 137 * DESCRIPTION: tag sort function for qsort() 138 */ 139static 140int tag_compare(const void *a, const void *b) 141{ 142 struct filetag const *tag1 = a, *tag2 = b; 143 144 if (tag1->location < tag2->location) 145 return -1; 146 else if (tag1->location > tag2->location) 147 return +1; 148 149 return 0; 150} 151 152/* 153 * NAME: add_filetag() 154 * DESCRIPTION: add a new file tag entry 155 */ 156static 157int add_filetag(struct id3_file *file, struct filetag const *filetag) 158{ 159 struct filetag *tags; 160 161 tags = realloc(file->tags, (file->ntags + 1) * sizeof(*tags)); 162 if (tags == 0) 163 return -1; 164 165 file->tags = tags; 166 file->tags[file->ntags++] = *filetag; 167 168 /* sort tags by location */ 169 170 if (file->ntags > 1) 171 qsort(file->tags, file->ntags, sizeof(file->tags[0]), tag_compare); 172 173 return 0; 174} 175 176/* 177 * NAME: del_filetag() 178 * DESCRIPTION: delete a file tag entry 179 */ 180static 181void del_filetag(struct id3_file *file, unsigned int index) 182{ 183 assert(index < file->ntags); 184 185 while (index < file->ntags - 1) { 186 file->tags[index] = file->tags[index + 1]; 187 ++index; 188 } 189 190 --file->ntags; 191} 192 193/* 194 * NAME: add_tag() 195 * DESCRIPTION: read, parse, and add a tag to a file structure 196 */ 197static 198struct id3_tag *add_tag(struct id3_file *file, id3_length_t length) 199{ 200 long location; 201 unsigned int i; 202 struct filetag filetag; 203 struct id3_tag *tag; 204 205 location = ftell(file->iofile); 206 if (location == -1) 207 return 0; 208 209 /* check for duplication/overlap */ 210 { 211 unsigned long begin1, end1, begin2, end2; 212 213 begin1 = location; 214 end1 = begin1 + length; 215 216 for (i = 0; i < file->ntags; ++i) { 217 begin2 = file->tags[i].location; 218 end2 = begin2 + file->tags[i].length; 219 220 if (begin1 == begin2 && end1 == end2) 221 return file->tags[i].tag; /* duplicate */ 222 223 if (begin1 < end2 && end1 > begin2) 224 return 0; /* overlap */ 225 } 226 } 227 228 tag = read_tag(file->iofile, length); 229 230 filetag.tag = tag; 231 filetag.location = location; 232 filetag.length = length; 233 234 if (add_filetag(file, &filetag) == -1 || 235 update_primary(file->primary, tag) == -1) { 236 if (tag) 237 id3_tag_delete(tag); 238 return 0; 239 } 240 241 if (tag) 242 id3_tag_addref(tag); 243 244 return tag; 245} 246 247/* 248 * NAME: search_tags() 249 * DESCRIPTION: search for tags in a file 250 */ 251static 252int search_tags(struct id3_file *file) 253{ 254 fpos_t save_position; 255 signed long size; 256 257 /* 258 * save the current seek position 259 * 260 * We also verify the stream is seekable by calling fsetpos(), since 261 * fgetpos() alone is not reliable enough for this purpose. 262 * 263 * [Apparently not even fsetpos() is sufficient under Win32.] 264 */ 265 266 if (fgetpos(file->iofile, &save_position) == -1 || 267 fsetpos(file->iofile, &save_position) == -1) 268 return -1; 269 270 /* look for an ID3v1 tag */ 271 272 if (fseek(file->iofile, -128, SEEK_END) == 0) { 273 size = query_tag(file->iofile); 274 if (size > 0) { 275 struct id3_tag const *tag; 276 277 tag = add_tag(file, size); 278 279 /* if this is indeed an ID3v1 tag, mark the file so */ 280 281 if (tag && (ID3_TAG_VERSION_MAJOR(id3_tag_version(tag)) == 1)) 282 file->flags |= ID3_FILE_FLAG_ID3V1; 283 } 284 } 285 286 /* look for a tag at the beginning of the file */ 287 288 rewind(file->iofile); 289 290 size = query_tag(file->iofile); 291 if (size > 0) { 292 struct id3_tag const *tag; 293 struct id3_frame const *frame; 294 295 tag = add_tag(file, size); 296 297 /* locate tags indicated by SEEK frames */ 298 299 while (tag && (frame = id3_tag_findframe(tag, "SEEK", 0))) { 300 long seek; 301 302 seek = id3_field_getint(id3_frame_field(frame, 0)); 303 if (seek < 0 || fseek(file->iofile, seek, SEEK_CUR) == -1) 304 break; 305 306 size = query_tag(file->iofile); 307 tag = (size > 0) ? add_tag(file, size) : 0; 308 } 309 } 310 311 /* look for a tag at the end of the file (before any ID3v1 tag) */ 312 313 if (fseek(file->iofile, ((file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0) + 314 -10, SEEK_END) == 0) { 315 size = query_tag(file->iofile); 316 if (size < 0 && fseek(file->iofile, size, SEEK_CUR) == 0) { 317 size = query_tag(file->iofile); 318 if (size > 0) 319 add_tag(file, size); 320 } 321 } 322 323 clearerr(file->iofile); 324 325 /* restore seek position */ 326 327 if (fsetpos(file->iofile, &save_position) == -1) 328 return -1; 329 330 /* set primary tag options and target padded length for convenience */ 331 332 if ((file->ntags > 0 && !(file->flags & ID3_FILE_FLAG_ID3V1)) || 333 (file->ntags > 1 && (file->flags & ID3_FILE_FLAG_ID3V1))) { 334 if (file->tags[0].location == 0) 335 id3_tag_setlength(file->primary, file->tags[0].length); 336 else 337 id3_tag_options(file->primary, ID3_TAG_OPTION_APPENDEDTAG, ~0); 338 } 339 340 return 0; 341} 342 343/* 344 * NAME: finish_file() 345 * DESCRIPTION: release memory associated with a file 346 */ 347static 348void finish_file(struct id3_file *file) 349{ 350 unsigned int i; 351 352 if (file->path) 353 free(file->path); 354 355 if (file->primary) { 356 id3_tag_delref(file->primary); 357 id3_tag_delete(file->primary); 358 } 359 360 for (i = 0; i < file->ntags; ++i) { 361 struct id3_tag *tag; 362 363 tag = file->tags[i].tag; 364 if (tag) { 365 id3_tag_delref(tag); 366 id3_tag_delete(tag); 367 } 368 } 369 370 if (file->tags) 371 free(file->tags); 372 373 free(file); 374} 375 376/* 377 * NAME: new_file() 378 * DESCRIPTION: create a new file structure and load tags 379 */ 380static 381struct id3_file *new_file(FILE *iofile, enum id3_file_mode mode, 382 char const *path) 383{ 384 struct id3_file *file; 385 386 file = malloc(sizeof(*file)); 387 if (file == 0) 388 goto fail; 389 390 file->iofile = iofile; 391 file->mode = mode; 392 file->path = path ? strdup(path) : 0; 393 394 file->flags = 0; 395 396 file->ntags = 0; 397 file->tags = 0; 398 399 file->primary = id3_tag_new(); 400 if (file->primary == 0) 401 goto fail; 402 403 id3_tag_addref(file->primary); 404 405 /* load tags from the file */ 406 407 if (search_tags(file) == -1) 408 goto fail; 409 410 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 411 (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0); 412 413 if (0) { 414 fail: 415 if (file) { 416 finish_file(file); 417 file = 0; 418 } 419 } 420 421 return file; 422} 423 424/* 425 * NAME: file->open() 426 * DESCRIPTION: open a file given its pathname 427 */ 428struct id3_file *id3_file_open(char const *path, enum id3_file_mode mode) 429{ 430 FILE *iofile; 431 struct id3_file *file; 432 433 assert(path); 434 435 iofile = fopen(path, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb"); 436 if (iofile == 0) 437 return 0; 438 439 file = new_file(iofile, mode, path); 440 if (file == 0) 441 fclose(iofile); 442 443 return file; 444} 445 446/* 447 * NAME: file->fdopen() 448 * DESCRIPTION: open a file using an existing file descriptor 449 */ 450struct id3_file *id3_file_fdopen(int fd, enum id3_file_mode mode) 451{ 452# if 1 || defined(HAVE_UNISTD_H) 453 FILE *iofile; 454 struct id3_file *file; 455 456 iofile = fdopen(fd, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb"); 457 if (iofile == 0) 458 return 0; 459 460 file = new_file(iofile, mode, 0); 461 if (file == 0) { 462 int save_fd; 463 464 /* close iofile without closing fd */ 465 466 save_fd = dup(fd); 467 468 fclose(iofile); 469 470 dup2(save_fd, fd); 471 close(save_fd); 472 } 473 474 return file; 475# else 476 return 0; 477# endif 478} 479 480/* 481 * NAME: file->close() 482 * DESCRIPTION: close a file and delete its associated tags 483 */ 484int id3_file_close(struct id3_file *file) 485{ 486 int result = 0; 487 488 assert(file); 489 490 if (fclose(file->iofile) == EOF) 491 result = -1; 492 493 finish_file(file); 494 495 return result; 496} 497 498/* 499 * NAME: file->tag() 500 * DESCRIPTION: return the primary tag structure for a file 501 */ 502struct id3_tag *id3_file_tag(struct id3_file const *file) 503{ 504 assert(file); 505 506 return file->primary; 507} 508 509/* 510 * NAME: v1_write() 511 * DESCRIPTION: write ID3v1 tag modifications to a file 512 */ 513static 514int v1_write(struct id3_file *file, 515 id3_byte_t const *data, id3_length_t length) 516{ 517 assert(!data || length == 128); 518 519 if (data) { 520 long location; 521 522 if (fseek(file->iofile, (file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0, 523 SEEK_END) == -1 || 524 (location = ftell(file->iofile)) == -1 || 525 fwrite(data, 128, 1, file->iofile) != 1 || 526 fflush(file->iofile) == EOF) 527 return -1; 528 529 /* add file tag reference */ 530 531 if (!(file->flags & ID3_FILE_FLAG_ID3V1)) { 532 struct filetag filetag; 533 534 filetag.tag = 0; 535 filetag.location = location; 536 filetag.length = 128; 537 538 if (add_filetag(file, &filetag) == -1) 539 return -1; 540 541 file->flags |= ID3_FILE_FLAG_ID3V1; 542 } 543 } 544# if defined(HAVE_FTRUNCATE) 545 else if (file->flags & ID3_FILE_FLAG_ID3V1) { 546 long length; 547 548 if (fseek(file->iofile, 0, SEEK_END) == -1) 549 return -1; 550 551 length = ftell(file->iofile); 552 if (length == -1 || 553 (length >= 0 && length < 128)) 554 return -1; 555 556 if (ftruncate(fileno(file->iofile), length - 128) == -1) 557 return -1; 558 559 /* delete file tag reference */ 560 561 del_filetag(file, file->ntags - 1); 562 563 file->flags &= ~ID3_FILE_FLAG_ID3V1; 564 } 565# endif 566 567 return 0; 568} 569 570/* 571 * NAME: v2_write() 572 * DESCRIPTION: write ID3v2 tag modifications to a file 573 */ 574static 575int v2_write(struct id3_file *file, 576 id3_byte_t const *data, id3_length_t length) 577{ 578 assert(!data || length > 0); 579 580 if (data && 581 ((file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) || 582 (file->ntags == 2 && (file->flags & ID3_FILE_FLAG_ID3V1))) && 583 file->tags[0].length == length) { 584 /* easy special case: rewrite existing tag in-place */ 585 586 if (fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 || 587 fwrite(data, length, 1, file->iofile) != 1 || 588 fflush(file->iofile) == EOF) 589 return -1; 590 591 goto done; 592 } 593 594 /* hard general case: rewrite entire file */ 595 596 /* ... */ 597 598 done: 599 return 0; 600} 601 602/* 603 * NAME: file->update() 604 * DESCRIPTION: rewrite tag(s) to a file 605 */ 606int id3_file_update(struct id3_file *file) 607{ 608 int options, result = 0; 609 id3_length_t v1size = 0, v2size = 0; 610 id3_byte_t id3v1_data[128], *id3v1 = 0, *id3v2 = 0; 611 612 assert(file); 613 614 if (file->mode != ID3_FILE_MODE_READWRITE) 615 return -1; 616 617 options = id3_tag_options(file->primary, 0, 0); 618 619 /* render ID3v1 */ 620 621 if (options & ID3_TAG_OPTION_ID3V1) { 622 v1size = id3_tag_render(file->primary, 0); 623 if (v1size) { 624 assert(v1size == sizeof(id3v1_data)); 625 626 v1size = id3_tag_render(file->primary, id3v1_data); 627 if (v1size) { 628 assert(v1size == sizeof(id3v1_data)); 629 id3v1 = id3v1_data; 630 } 631 } 632 } 633 634 /* render ID3v2 */ 635 636 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0); 637 638 v2size = id3_tag_render(file->primary, 0); 639 if (v2size) { 640 id3v2 = malloc(v2size); 641 if (id3v2 == 0) 642 goto fail; 643 644 v2size = id3_tag_render(file->primary, id3v2); 645 if (v2size == 0) { 646 free(id3v2); 647 id3v2 = 0; 648 } 649 } 650 651 /* write tags */ 652 653 if (v2_write(file, id3v2, v2size) == -1 || 654 v1_write(file, id3v1, v1size) == -1) 655 goto fail; 656 657 rewind(file->iofile); 658 659 /* update file tags array? ... */ 660 661 if (0) { 662 fail: 663 result = -1; 664 } 665 666 /* clean up; restore tag options */ 667 668 if (id3v2) 669 free(id3v2); 670 671 id3_tag_options(file->primary, ~0, options); 672 673 return result; 674} 675