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