001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.commons.compress.archivers.dump; 020 021 import java.util.Collections; 022 import java.util.Date; 023 import java.util.EnumSet; 024 import java.util.HashSet; 025 import java.util.Set; 026 import org.apache.commons.compress.archivers.ArchiveEntry; 027 028 /** 029 * This class represents an entry in a Dump archive. It consists 030 * of the entry's header, the entry's File and any extended attributes. 031 * <p> 032 * DumpEntries that are created from the header bytes read from 033 * an archive are instantiated with the DumpArchiveEntry( byte[] ) 034 * constructor. These entries will be used when extracting from 035 * or listing the contents of an archive. These entries have their 036 * header filled in using the header bytes. They also set the File 037 * to null, since they reference an archive entry not a file. 038 * <p> 039 * DumpEntries can also be constructed from nothing but a name. 040 * This allows the programmer to construct the entry by hand, for 041 * instance when only an InputStream is available for writing to 042 * the archive, and the header information is constructed from 043 * other information. In this case the header fields are set to 044 * defaults and the File is set to null. 045 * 046 * <p> 047 * The C structure for a Dump Entry's header is: 048 * <pre> 049 * #define TP_BSIZE 1024 // size of each file block 050 * #define NTREC 10 // number of blocks to write at once 051 * #define HIGHDENSITYTREC 32 // number of blocks to write on high-density tapes 052 * #define TP_NINDIR (TP_BSIZE/2) // number if indirect inodes in record 053 * #define TP_NINOS (TP_NINDIR / sizeof (int32_t)) 054 * #define LBLSIZE 16 055 * #define NAMELEN 64 056 * 057 * #define OFS_MAGIC (int)60011 // old format magic value 058 * #define NFS_MAGIC (int)60012 // new format magic value 059 * #define FS_UFS2_MAGIC (int)0x19540119 060 * #define CHECKSUM (int)84446 // constant used in checksum algorithm 061 * 062 * struct s_spcl { 063 * int32_t c_type; // record type (see below) 064 * int32_t <b>c_date</b>; // date of this dump 065 * int32_t <b>c_ddate</b>; // date of previous dump 066 * int32_t c_volume; // dump volume number 067 * u_int32_t c_tapea; // logical block of this record 068 * dump_ino_t c_ino; // number of inode 069 * int32_t <b>c_magic</b>; // magic number (see above) 070 * int32_t c_checksum; // record checksum 071 * #ifdef __linux__ 072 * struct new_bsd_inode c_dinode; 073 * #else 074 * #ifdef sunos 075 * struct new_bsd_inode c_dinode; 076 * #else 077 * struct dinode c_dinode; // ownership and mode of inode 078 * #endif 079 * #endif 080 * int32_t c_count; // number of valid c_addr entries 081 * union u_data c_data; // see above 082 * char <b>c_label[LBLSIZE]</b>; // dump label 083 * int32_t <b>c_level</b>; // level of this dump 084 * char <b>c_filesys[NAMELEN]</b>; // name of dumpped file system 085 * char <b>c_dev[NAMELEN]</b>; // name of dumpped device 086 * char <b>c_host[NAMELEN]</b>; // name of dumpped host 087 * int32_t c_flags; // additional information (see below) 088 * int32_t c_firstrec; // first record on volume 089 * int32_t c_ntrec; // blocksize on volume 090 * int32_t c_extattributes; // additional inode info (see below) 091 * int32_t c_spare[30]; // reserved for future uses 092 * } s_spcl; 093 * 094 * // 095 * // flag values 096 * // 097 * #define DR_NEWHEADER 0x0001 // new format tape header 098 * #define DR_NEWINODEFMT 0x0002 // new format inodes on tape 099 * #define DR_COMPRESSED 0x0080 // dump tape is compressed 100 * #define DR_METAONLY 0x0100 // only the metadata of the inode has been dumped 101 * #define DR_INODEINFO 0x0002 // [SIC] TS_END header contains c_inos information 102 * #define DR_EXTATTRIBUTES 0x8000 103 * 104 * // 105 * // extattributes inode info 106 * // 107 * #define EXT_REGULAR 0 108 * #define EXT_MACOSFNDRINFO 1 109 * #define EXT_MACOSRESFORK 2 110 * #define EXT_XATTR 3 111 * 112 * // used for EA on tape 113 * #define EXT2_GOOD_OLD_INODE_SIZE 128 114 * #define EXT2_XATTR_MAGIC 0xEA020000 // block EA 115 * #define EXT2_XATTR_MAGIC2 0xEA020001 // in inode EA 116 * </pre> 117 * The fields in <b>bold</b> are the same for all blocks. (This permitted 118 * multiple dumps to be written to a single tape.) 119 * </p> 120 * 121 * <p> 122 * The C structure for the inode (file) information is: 123 * <pre> 124 * struct bsdtimeval { // **** alpha-*-linux is deviant 125 * __u32 tv_sec; 126 * __u32 tv_usec; 127 * }; 128 * 129 * #define NDADDR 12 130 * #define NIADDR 3 131 * 132 * // 133 * // This is the new (4.4) BSD inode structure 134 * // copied from the FreeBSD 2.0 <ufs/ufs/dinode.h> include file 135 * // 136 * struct new_bsd_inode { 137 * __u16 di_mode; // file type, standard Unix permissions 138 * __s16 di_nlink; // number of hard links to file. 139 * union { 140 * __u16 oldids[2]; 141 * __u32 inumber; 142 * } di_u; 143 * u_quad_t di_size; // file size 144 * struct bsdtimeval di_atime; // time file was last accessed 145 * struct bsdtimeval di_mtime; // time file was last modified 146 * struct bsdtimeval di_ctime; // time file was created 147 * __u32 di_db[NDADDR]; 148 * __u32 di_ib[NIADDR]; 149 * __u32 di_flags; // 150 * __s32 di_blocks; // number of disk blocks 151 * __s32 di_gen; // generation number 152 * __u32 di_uid; // user id (see /etc/passwd) 153 * __u32 di_gid; // group id (see /etc/group) 154 * __s32 di_spare[2]; // unused 155 * }; 156 * </pre> 157 * It is important to note that the header DOES NOT have the name of the 158 * file. It can't since hard links mean that you may have multiple filenames 159 * for a single physical file. You must read the contents of the directory 160 * entries to learn the mapping(s) from filename to inode. 161 * </p> 162 * 163 * <p> 164 * The C structure that indicates if a specific block is a real block 165 * that contains data or is a sparse block that is not persisted to the 166 * disk is: 167 * <pre> 168 * #define TP_BSIZE 1024 169 * #define TP_NINDIR (TP_BSIZE/2) 170 * 171 * union u_data { 172 * char s_addrs[TP_NINDIR]; // 1 => data; 0 => hole in inode 173 * int32_t s_inos[TP_NINOS]; // table of first inode on each volume 174 * } u_data; 175 * </pre></p> 176 * 177 * @NotThreadSafe 178 */ 179 public class DumpArchiveEntry implements ArchiveEntry { 180 private String name; 181 private TYPE type = TYPE.UNKNOWN; 182 private int mode; 183 private Set<PERMISSION> permissions = Collections.emptySet(); 184 private long size; 185 private long atime; 186 private long mtime; 187 private int uid; 188 private int gid; 189 190 /** 191 * Currently unused 192 */ 193 private DumpArchiveSummary summary = null; 194 195 // this information is available from standard index. 196 private TapeSegmentHeader header = new TapeSegmentHeader(); 197 private String simpleName; 198 private String originalName; 199 200 // this information is available from QFA index 201 private int volume; 202 private long offset; 203 private int ino; 204 private int nlink; 205 private long ctime; 206 private int generation; 207 private boolean isDeleted; 208 209 /** 210 * Default constructor. 211 */ 212 public DumpArchiveEntry() { 213 } 214 215 /** 216 * Constructor taking only filename. 217 * @param name pathname 218 * @param simpleName actual filename. 219 */ 220 public DumpArchiveEntry(String name, String simpleName) { 221 setName(name); 222 this.simpleName = simpleName; 223 } 224 225 /** 226 * Constructor taking name, inode and type. 227 * 228 * @param name 229 * @param simpleName 230 * @param ino 231 * @param type 232 */ 233 protected DumpArchiveEntry(String name, String simpleName, int ino, 234 TYPE type) { 235 setType(type); 236 setName(name); 237 this.simpleName = simpleName; 238 this.ino = ino; 239 this.offset = 0; 240 } 241 242 /** 243 * Constructor taking tape buffer. 244 * @param buffer 245 * @param offset 246 */ 247 248 /** 249 * Returns the path of the entry. 250 * @return the path of the entry. 251 */ 252 public String getSimpleName() { 253 return simpleName; 254 } 255 256 /** 257 * Sets the path of the entry. 258 */ 259 protected void setSimpleName(String simpleName) { 260 this.simpleName = simpleName; 261 } 262 263 /** 264 * Returns the ino of the entry. 265 */ 266 public int getIno() { 267 return header.getIno(); 268 } 269 270 /** 271 * Return the number of hard links to the entry. 272 */ 273 public int getNlink() { 274 return nlink; 275 } 276 277 /** 278 * Set the number of hard links. 279 */ 280 public void setNlink(int nlink) { 281 this.nlink = nlink; 282 } 283 284 /** 285 * Get file creation time. 286 */ 287 public Date getCreationTime() { 288 return new Date(ctime); 289 } 290 291 /** 292 * Set the file creation time. 293 */ 294 public void setCreationTime(Date ctime) { 295 this.ctime = ctime.getTime(); 296 } 297 298 /** 299 * Return the generation of the file. 300 */ 301 public int getGeneration() { 302 return generation; 303 } 304 305 /** 306 * Set the generation of the file. 307 */ 308 public void setGeneration(int generation) { 309 this.generation = generation; 310 } 311 312 /** 313 * Has this file been deleted? (On valid on incremental dumps.) 314 */ 315 public boolean isDeleted() { 316 return isDeleted; 317 } 318 319 /** 320 * Set whether this file has been deleted. 321 */ 322 public void setDeleted(boolean isDeleted) { 323 this.isDeleted = isDeleted; 324 } 325 326 /** 327 * Return the offset within the archive 328 */ 329 public long getOffset() { 330 return offset; 331 } 332 333 /** 334 * Set the offset within the archive. 335 */ 336 public void setOffset(long offset) { 337 this.offset = offset; 338 } 339 340 /** 341 * Return the tape volume where this file is located. 342 */ 343 public int getVolume() { 344 return volume; 345 } 346 347 /** 348 * Set the tape volume. 349 */ 350 public void setVolume(int volume) { 351 this.volume = volume; 352 } 353 354 /** 355 * Return the type of the tape segment header. 356 */ 357 public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() { 358 return header.getType(); 359 } 360 361 /** 362 * Return the number of records in this segment. 363 */ 364 public int getHeaderCount() { 365 return header.getCount(); 366 } 367 368 /** 369 * Return the number of sparse records in this segment. 370 */ 371 public int getHeaderHoles() { 372 return header.getHoles(); 373 } 374 375 /** 376 * Is this a sparse record? 377 */ 378 public boolean isSparseRecord(int idx) { 379 return (header.getCdata(idx) & 0x01) == 0; 380 } 381 382 /** 383 * @see java.lang.Object#hashCode() 384 */ 385 @Override 386 public int hashCode() { 387 return ino; 388 } 389 390 /** 391 * @see java.lang.Object#equals(Object o) 392 */ 393 @Override 394 public boolean equals(Object o) { 395 if (o == this) { 396 return true; 397 } else if (o == null || !o.getClass().equals(getClass())) { 398 return false; 399 } 400 401 DumpArchiveEntry rhs = (DumpArchiveEntry) o; 402 403 if ((header == null) || (rhs.header == null)) { 404 return false; 405 } 406 407 if (ino != rhs.ino) { 408 return false; 409 } 410 411 if ((summary == null && rhs.summary != null) 412 || (summary != null && !summary.equals(rhs.summary))) { 413 return false; 414 } 415 416 return true; 417 } 418 419 /** 420 * @see java.lang.Object#toString() 421 */ 422 @Override 423 public String toString() { 424 return getName(); 425 } 426 427 /** 428 * Populate the dump archive entry and tape segment header with 429 * the contents of the buffer. 430 * 431 * @param buffer 432 * @throws Exception 433 */ 434 static DumpArchiveEntry parse(byte[] buffer) { 435 DumpArchiveEntry entry = new DumpArchiveEntry(); 436 TapeSegmentHeader header = entry.header; 437 438 header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32( 439 buffer, 0)); 440 441 //header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4)); 442 //header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32( 443 // buffer, 8)); 444 header.volume = DumpArchiveUtil.convert32(buffer, 12); 445 //header.tapea = DumpArchiveUtil.convert32(buffer, 16); 446 entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20); 447 448 //header.magic = DumpArchiveUtil.convert32(buffer, 24); 449 //header.checksum = DumpArchiveUtil.convert32(buffer, 28); 450 int m = DumpArchiveUtil.convert16(buffer, 32); 451 452 // determine the type of the file. 453 entry.setType(TYPE.find((m >> 12) & 0x0F)); 454 455 // determine the standard permissions 456 entry.setMode(m); 457 458 entry.nlink = DumpArchiveUtil.convert16(buffer, 34); 459 // inumber, oldids? 460 entry.setSize(DumpArchiveUtil.convert64(buffer, 40)); 461 462 long t = (1000L * DumpArchiveUtil.convert32(buffer, 48)) + 463 (DumpArchiveUtil.convert32(buffer, 52) / 1000); 464 entry.setAccessTime(new Date(t)); 465 t = (1000L * DumpArchiveUtil.convert32(buffer, 56)) + 466 (DumpArchiveUtil.convert32(buffer, 60) / 1000); 467 entry.setLastModifiedDate(new Date(t)); 468 t = (1000L * DumpArchiveUtil.convert32(buffer, 64)) + 469 (DumpArchiveUtil.convert32(buffer, 68) / 1000); 470 entry.ctime = t; 471 472 // db: 72-119 - direct blocks 473 // id: 120-131 - indirect blocks 474 //entry.flags = DumpArchiveUtil.convert32(buffer, 132); 475 //entry.blocks = DumpArchiveUtil.convert32(buffer, 136); 476 entry.generation = DumpArchiveUtil.convert32(buffer, 140); 477 entry.setUserId(DumpArchiveUtil.convert32(buffer, 144)); 478 entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148)); 479 // two 32-bit spare values. 480 header.count = DumpArchiveUtil.convert32(buffer, 160); 481 482 header.holes = 0; 483 484 for (int i = 0; (i < 512) && (i < header.count); i++) { 485 if (buffer[164 + i] == 0) { 486 header.holes++; 487 } 488 } 489 490 System.arraycopy(buffer, 164, header.cdata, 0, 512); 491 492 entry.volume = header.getVolume(); 493 494 //entry.isSummaryOnly = false; 495 return entry; 496 } 497 498 /** 499 * Update entry with information from next tape segment header. 500 */ 501 void update(byte[] buffer) { 502 header.volume = DumpArchiveUtil.convert32(buffer, 16); 503 header.count = DumpArchiveUtil.convert32(buffer, 160); 504 505 header.holes = 0; 506 507 for (int i = 0; (i < 512) && (i < header.count); i++) { 508 if (buffer[164 + i] == 0) { 509 header.holes++; 510 } 511 } 512 513 System.arraycopy(buffer, 164, header.cdata, 0, 512); 514 } 515 516 /** 517 * Archive entry as stored on tape. There is one TSH for (at most) 518 * every 512k in the file. 519 */ 520 static class TapeSegmentHeader { 521 private DumpArchiveConstants.SEGMENT_TYPE type; 522 private int volume; 523 private int ino; 524 private int count; 525 private int holes; 526 private byte[] cdata = new byte[512]; // map of any 'holes' 527 528 public DumpArchiveConstants.SEGMENT_TYPE getType() { 529 return type; 530 } 531 532 public int getVolume() { 533 return volume; 534 } 535 536 public int getIno() { 537 return ino; 538 } 539 540 void setIno(int ino) { 541 this.ino = ino; 542 } 543 544 public int getCount() { 545 return count; 546 } 547 548 public int getHoles() { 549 return holes; 550 } 551 552 public int getCdata(int idx) { 553 return cdata[idx]; 554 } 555 } 556 557 /** 558 * Returns the name of the entry. 559 * @return the name of the entry. 560 */ 561 public String getName() { 562 return name; 563 } 564 565 /** 566 * Returns the unmodified name of the entry. 567 * @return the name of the entry. 568 */ 569 String getOriginalName() { 570 return originalName; 571 } 572 573 /** 574 * Sets the name of the entry. 575 */ 576 public final void setName(String name) { 577 this.originalName = name; 578 if (name != null) { 579 if (isDirectory() && !name.endsWith("/")) { 580 name += "/"; 581 } 582 if (name.startsWith("./")) { 583 name = name.substring(2); 584 } 585 } 586 this.name = name; 587 } 588 589 /** {@inheritDoc} */ 590 public Date getLastModifiedDate() { 591 return new Date(mtime); 592 } 593 594 /** 595 * Is this a directory? 596 */ 597 public boolean isDirectory() { 598 return type == TYPE.DIRECTORY; 599 } 600 601 /** 602 * Is this a regular file? 603 */ 604 public boolean isFile() { 605 return type == TYPE.FILE; 606 } 607 608 /** 609 * Is this a network device? 610 */ 611 public boolean isSocket() { 612 return type == TYPE.SOCKET; 613 } 614 615 /** 616 * Is this a character device? 617 */ 618 public boolean isChrDev() { 619 return type == TYPE.CHRDEV; 620 } 621 622 /** 623 * Is this a block device? 624 */ 625 public boolean isBlkDev() { 626 return type == TYPE.BLKDEV; 627 } 628 629 /** 630 * Is this a fifo/pipe? 631 */ 632 public boolean isFifo() { 633 return type == TYPE.FIFO; 634 } 635 636 /** 637 * Get the type of the entry. 638 */ 639 public TYPE getType() { 640 return type; 641 } 642 643 /** 644 * Set the type of the entry. 645 */ 646 public void setType(TYPE type) { 647 this.type = type; 648 } 649 650 /** 651 * Return the access permissions on the entry. 652 */ 653 public int getMode() { 654 return mode; 655 } 656 657 /** 658 * Set the access permissions on the entry. 659 */ 660 public void setMode(int mode) { 661 this.mode = mode & 07777; 662 this.permissions = PERMISSION.find(mode); 663 } 664 665 /** 666 * Returns the permissions on the entry. 667 */ 668 public Set<PERMISSION> getPermissions() { 669 return permissions; 670 } 671 672 /** 673 * Returns the size of the entry. 674 */ 675 public long getSize() { 676 return isDirectory() ? SIZE_UNKNOWN : size; 677 } 678 679 /** 680 * Returns the size of the entry as read from the archive. 681 */ 682 long getEntrySize() { 683 return size; 684 } 685 686 /** 687 * Set the size of the entry. 688 */ 689 public void setSize(long size) { 690 this.size = size; 691 } 692 693 /** 694 * Set the time the file was last modified. 695 */ 696 public void setLastModifiedDate(Date mtime) { 697 this.mtime = mtime.getTime(); 698 } 699 700 /** 701 * Returns the time the file was last accessed. 702 */ 703 public Date getAccessTime() { 704 return new Date(atime); 705 } 706 707 /** 708 * Set the time the file was last accessed. 709 */ 710 public void setAccessTime(Date atime) { 711 this.atime = atime.getTime(); 712 } 713 714 /** 715 * Return the user id. 716 */ 717 public int getUserId() { 718 return uid; 719 } 720 721 /** 722 * Set the user id. 723 */ 724 public void setUserId(int uid) { 725 this.uid = uid; 726 } 727 728 /** 729 * Return the group id 730 */ 731 public int getGroupId() { 732 return gid; 733 } 734 735 /** 736 * Set the group id. 737 */ 738 public void setGroupId(int gid) { 739 this.gid = gid; 740 } 741 742 public enum TYPE { 743 WHITEOUT(14), 744 SOCKET(12), 745 LINK(10), 746 FILE(8), 747 BLKDEV(6), 748 DIRECTORY(4), 749 CHRDEV(2), 750 FIFO(1), 751 UNKNOWN(15); 752 753 private int code; 754 755 private TYPE(int code) { 756 this.code = code; 757 } 758 759 public static TYPE find(int code) { 760 TYPE type = UNKNOWN; 761 762 for (TYPE t : TYPE.values()) { 763 if (code == t.code) { 764 type = t; 765 } 766 } 767 768 return type; 769 } 770 } 771 772 public enum PERMISSION { 773 SETUID(04000), 774 SETGUI(02000), 775 STICKY(01000), 776 USER_READ(00400), 777 USER_WRITE(00200), 778 USER_EXEC(00100), 779 GROUP_READ(00040), 780 GROUP_WRITE(00020), 781 GROUP_EXEC(00010), 782 WORLD_READ(00004), 783 WORLD_WRITE(00002), 784 WORLD_EXEC(00001); 785 786 private int code; 787 788 private PERMISSION(int code) { 789 this.code = code; 790 } 791 792 public static Set<PERMISSION> find(int code) { 793 Set<PERMISSION> set = new HashSet<PERMISSION>(); 794 795 for (PERMISSION p : PERMISSION.values()) { 796 if ((code & p.code) == p.code) { 797 set.add(p); 798 } 799 } 800 801 if (set.isEmpty()) { 802 return Collections.emptySet(); 803 } 804 805 return EnumSet.copyOf(set); 806 } 807 } 808 }