1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.commons.compress.archivers.dump; 20 21 import java.util.Collections; 22 import java.util.Date; 23 import java.util.EnumSet; 24 import java.util.HashSet; 25 import java.util.Set; 26 27 import org.apache.commons.compress.archivers.ArchiveEntry; 28 29 /** 30 * This class represents an entry in a Dump archive. It consists of the entry's header, the entry's File and any extended attributes. 31 * <p> 32 * DumpEntries that are created from the header bytes read from an archive are instantiated with the DumpArchiveEntry( byte[] ) constructor. These entries will 33 * 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 34 * to null, since they reference an archive entry not a file. 35 * <p> 36 * 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 37 * 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 38 * and the File is set to null. 39 * 40 * <p> 41 * The C structure for a Dump Entry's header is: 42 * 43 * <pre> 44 * #define TP_BSIZE 1024 // size of each file block 45 * #define NTREC 10 // number of blocks to write at once 46 * #define HIGHDENSITYTREC 32 // number of blocks to write on high-density tapes 47 * #define TP_NINDIR (TP_BSIZE/2) // number if indirect inodes in record 48 * #define TP_NINOS (TP_NINDIR / sizeof (int32_t)) 49 * #define LBLSIZE 16 50 * #define NAMELEN 64 51 * 52 * #define OFS_MAGIC (int) 60011 // old format magic value 53 * #define NFS_MAGIC (int) 60012 // new format magic value 54 * #define FS_UFS2_MAGIC (int) 0x19540119 55 * #define CHECKSUM (int) 84446 // constant used in checksum algorithm 56 * 57 * struct s_spcl { 58 * int32_t c_type; // record type (see below) 59 * int32_t <b>c_date</b>; // date of this dump 60 * int32_t <b>c_ddate</b>; // date of previous dump 61 * int32_t c_volume; // dump volume number 62 * u_int32_t c_tapea; // logical block of this record 63 * dump_ino_t c_ino; // number of inode 64 * int32_t <b>c_magic</b>; // magic number (see above) 65 * int32_t c_checksum; // record checksum 66 * #ifdef __linux__ 67 * struct new_bsd_inode c_dinode; 68 * #else 69 * #ifdef sunos 70 * struct new_bsd_inode c_dinode; 71 * #else 72 * struct dinode c_dinode; // ownership and mode of inode 73 * #endif 74 * #endif 75 * int32_t c_count; // number of valid c_addr entries 76 * union u_data c_data; // see above 77 * char <b>c_label[LBLSIZE]</b>; // dump label 78 * int32_t <b>c_level</b>; // level of this dump 79 * char <b>c_filesys[NAMELEN]</b>; // name of dumpped file system 80 * char <b>c_dev[NAMELEN]</b>; // name of dumpped device 81 * char <b>c_host[NAMELEN]</b>; // name of dumpped host 82 * int32_t c_flags; // additional information (see below) 83 * int32_t c_firstrec; // first record on volume 84 * int32_t c_ntrec; // blocksize on volume 85 * int32_t c_extattributes; // additional inode info (see below) 86 * int32_t c_spare[30]; // reserved for future uses 87 * } s_spcl; 88 * 89 * // 90 * // flag values 91 * // 92 * #define DR_NEWHEADER 0x0001 // new format tape header 93 * #define DR_NEWINODEFMT 0x0002 // new format inodes on tape 94 * #define DR_COMPRESSED 0x0080 // dump tape is compressed 95 * #define DR_METAONLY 0x0100 // only the metadata of the inode has been dumped 96 * #define DR_INODEINFO 0x0002 // [SIC] TS_END header contains c_inos information 97 * #define DR_EXTATTRIBUTES 0x8000 98 * 99 * // 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 */ 174 public class DumpArchiveEntry implements ArchiveEntry { 175 176 public enum PERMISSION { 177 // Note: The arguments are octal values 178 // @formatter:off 179 SETUID(04000), 180 SETGUI(02000), 181 STICKY(01000), 182 USER_READ(00400), 183 USER_WRITE(00200), 184 USER_EXEC(00100), 185 GROUP_READ(00040), 186 GROUP_WRITE(00020), 187 GROUP_EXEC(00010), 188 WORLD_READ(00004), 189 WORLD_WRITE(00002), 190 WORLD_EXEC(00001); 191 // @formatter:on 192 193 public static Set<PERMISSION> find(final int code) { 194 final Set<PERMISSION> set = new HashSet<>(); 195 196 for (final PERMISSION p : values()) { 197 if ((code & p.code) == p.code) { 198 set.add(p); 199 } 200 } 201 202 if (set.isEmpty()) { 203 return Collections.emptySet(); 204 } 205 206 return EnumSet.copyOf(set); 207 } 208 209 private final int code; 210 211 PERMISSION(final int code) { 212 this.code = code; 213 } 214 } 215 216 /** 217 * Archive entry as stored on tape. There is one TSH for (at most) every 512k in the file. 218 */ 219 static class TapeSegmentHeader { 220 private DumpArchiveConstants.SEGMENT_TYPE type; 221 private int volume; 222 private int ino; 223 private int count; 224 private int holes; 225 private final byte[] cdata = new byte[512]; // map of any 'holes' 226 227 public int getCdata(final int idx) { 228 return cdata[idx]; 229 } 230 231 public int getCount() { 232 return count; 233 } 234 235 public int getHoles() { 236 return holes; 237 } 238 239 public int getIno() { 240 return ino; 241 } 242 243 public DumpArchiveConstants.SEGMENT_TYPE getType() { 244 return type; 245 } 246 247 public int getVolume() { 248 return volume; 249 } 250 251 void setIno(final int ino) { 252 this.ino = ino; 253 } 254 } 255 256 public enum TYPE { 257 WHITEOUT(14), SOCKET(12), LINK(10), FILE(8), BLKDEV(6), DIRECTORY(4), CHRDEV(2), FIFO(1), UNKNOWN(15); 258 259 public static TYPE find(final int code) { 260 TYPE type = UNKNOWN; 261 262 for (final TYPE t : values()) { 263 if (code == t.code) { 264 type = t; 265 } 266 } 267 268 return type; 269 } 270 271 private final int code; 272 273 TYPE(final int code) { 274 this.code = code; 275 } 276 } 277 278 /** 279 * Populate the dump archive entry and tape segment header with the contents of the buffer. 280 * 281 * @param buffer buffer to read content from 282 */ 283 static DumpArchiveEntry parse(final byte[] buffer) { 284 final DumpArchiveEntry entry = new DumpArchiveEntry(); 285 final TapeSegmentHeader header = entry.header; 286 287 header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(buffer, 0)); 288 289 // header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4)); 290 // header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32( 291 // buffer, 8)); 292 header.volume = DumpArchiveUtil.convert32(buffer, 12); 293 // header.tapea = DumpArchiveUtil.convert32(buffer, 16); 294 entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20); 295 296 // header.magic = DumpArchiveUtil.convert32(buffer, 24); 297 // header.checksum = DumpArchiveUtil.convert32(buffer, 28); 298 final int m = DumpArchiveUtil.convert16(buffer, 32); 299 300 // determine the type of the file. 301 entry.setType(TYPE.find(m >> 12 & 0x0F)); 302 303 // determine the standard permissions 304 entry.setMode(m); 305 306 entry.nlink = DumpArchiveUtil.convert16(buffer, 34); 307 // inumber, oldids? 308 entry.setSize(DumpArchiveUtil.convert64(buffer, 40)); 309 310 long t = 1000L * DumpArchiveUtil.convert32(buffer, 48) + DumpArchiveUtil.convert32(buffer, 52) / 1000; 311 entry.setAccessTime(new Date(t)); 312 t = 1000L * DumpArchiveUtil.convert32(buffer, 56) + DumpArchiveUtil.convert32(buffer, 60) / 1000; 313 entry.setLastModifiedDate(new Date(t)); 314 t = 1000L * DumpArchiveUtil.convert32(buffer, 64) + DumpArchiveUtil.convert32(buffer, 68) / 1000; 315 entry.ctime = t; 316 317 // db: 72-119 - direct blocks 318 // id: 120-131 - indirect blocks 319 // entry.flags = DumpArchiveUtil.convert32(buffer, 132); 320 // entry.blocks = DumpArchiveUtil.convert32(buffer, 136); 321 entry.generation = DumpArchiveUtil.convert32(buffer, 140); 322 entry.setUserId(DumpArchiveUtil.convert32(buffer, 144)); 323 entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148)); 324 // two 32-bit spare values. 325 header.count = DumpArchiveUtil.convert32(buffer, 160); 326 327 header.holes = 0; 328 329 for (int i = 0; i < 512 && i < header.count; i++) { 330 if (buffer[164 + i] == 0) { 331 header.holes++; 332 } 333 } 334 335 System.arraycopy(buffer, 164, header.cdata, 0, 512); 336 337 entry.volume = header.getVolume(); 338 339 // entry.isSummaryOnly = false; 340 return entry; 341 } 342 343 private String name; 344 private TYPE type = TYPE.UNKNOWN; 345 private int mode; 346 private Set<PERMISSION> permissions = Collections.emptySet(); 347 private long size; 348 349 private long atime; 350 351 private long mtime; 352 private int uid; 353 private int gid; 354 355 /** 356 * Currently unused 357 */ 358 private final DumpArchiveSummary summary = null; 359 360 /** 361 * This value is available from the standard index. 362 */ 363 private final TapeSegmentHeader header = new TapeSegmentHeader(); 364 private String simpleName; 365 private String originalName; 366 367 /** 368 * This value is available from the QFA index. 369 */ 370 private int volume; 371 private long offset; 372 private int ino; 373 374 private int nlink; 375 376 private long ctime; 377 378 private int generation; 379 380 private boolean isDeleted; 381 382 /** 383 * Constructs a default instance. 384 */ 385 public DumpArchiveEntry() { 386 } 387 388 /** 389 * Constructs a new instance with only names. 390 * 391 * @param name path name 392 * @param simpleName actual file name. 393 */ 394 public DumpArchiveEntry(final String name, final String simpleName) { 395 setName(name); 396 this.simpleName = simpleName; 397 } 398 399 /** 400 * Constructs a new instance with name, inode and type. 401 * 402 * @param name the name 403 * @param simpleName the simple name 404 * @param ino the ino 405 * @param type the type 406 */ 407 protected DumpArchiveEntry(final String name, final String simpleName, final int ino, final TYPE type) { 408 setType(type); 409 setName(name); 410 this.simpleName = simpleName; 411 this.ino = ino; 412 this.offset = 0; 413 } 414 415 @Override 416 public boolean equals(final Object o) { 417 if (o == this) { 418 return true; 419 } 420 if (o == null || !o.getClass().equals(getClass())) { 421 return false; 422 } 423 424 final DumpArchiveEntry rhs = (DumpArchiveEntry) o; 425 426 if (ino != rhs.ino) { 427 return false; 428 } 429 430 // summary is always null right now, but this may change some day 431 if (summary == null && rhs.summary != null // NOSONAR 432 || summary != null && !summary.equals(rhs.summary)) { // NOSONAR 433 return false; 434 } 435 436 return true; 437 } 438 439 /** 440 * Returns the time the file was last accessed. 441 * 442 * @return the access time 443 */ 444 public Date getAccessTime() { 445 return new Date(atime); 446 } 447 448 /** 449 * Gets file creation time. 450 * 451 * @return the creation time 452 */ 453 public Date getCreationTime() { 454 return new Date(ctime); 455 } 456 457 /** 458 * Returns the size of the entry as read from the archive. 459 */ 460 long getEntrySize() { 461 return size; 462 } 463 464 /** 465 * Gets the generation of the file. 466 * 467 * @return the generation 468 */ 469 public int getGeneration() { 470 return generation; 471 } 472 473 /** 474 * Gets the group id 475 * 476 * @return the group id 477 */ 478 public int getGroupId() { 479 return gid; 480 } 481 482 /** 483 * Gets the number of records in this segment. 484 * 485 * @return the number of records 486 */ 487 public int getHeaderCount() { 488 return header.getCount(); 489 } 490 491 /** 492 * Gets the number of sparse records in this segment. 493 * 494 * @return the number of sparse records 495 */ 496 public int getHeaderHoles() { 497 return header.getHoles(); 498 } 499 500 /** 501 * Gets the type of the tape segment header. 502 * 503 * @return the segment header 504 */ 505 public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() { 506 return header.getType(); 507 } 508 509 /** 510 * Returns the ino of the entry. 511 * 512 * @return the ino 513 */ 514 public int getIno() { 515 return header.getIno(); 516 } 517 518 /** 519 * The last modified date. 520 * 521 * @return the last modified date 522 */ 523 @Override 524 public Date getLastModifiedDate() { 525 return new Date(mtime); 526 } 527 528 /** 529 * Gets the access permissions on the entry. 530 * 531 * @return the access permissions 532 */ 533 public int getMode() { 534 return mode; 535 } 536 537 /** 538 * Returns the name of the entry. 539 * 540 * <p> 541 * This method returns the raw name as it is stored inside of the archive. 542 * </p> 543 * 544 * @return the name of the entry. 545 */ 546 @Override 547 public String getName() { 548 return name; 549 } 550 551 /** 552 * Gets the number of hard links to the entry. 553 * 554 * @return the number of hard links 555 */ 556 public int getNlink() { 557 return nlink; 558 } 559 560 /** 561 * Gets the offset within the archive 562 * 563 * @return the offset 564 */ 565 public long getOffset() { 566 return offset; 567 } 568 569 /** 570 * Returns the unmodified name of the entry. 571 * 572 * @return the name of the entry. 573 */ 574 String getOriginalName() { 575 return originalName; 576 } 577 578 /** 579 * Returns the permissions on the entry. 580 * 581 * @return the permissions 582 */ 583 public Set<PERMISSION> getPermissions() { 584 return permissions; 585 } 586 587 /** 588 * Returns the path of the entry. 589 * 590 * @return the path of the entry. 591 */ 592 public String getSimpleName() { 593 return simpleName; 594 } 595 596 /** 597 * Returns the size of the entry. 598 * 599 * @return the size 600 */ 601 @Override 602 public long getSize() { 603 return isDirectory() ? SIZE_UNKNOWN : size; 604 } 605 606 /** 607 * Gets the type of the entry. 608 * 609 * @return the type 610 */ 611 public TYPE getType() { 612 return type; 613 } 614 615 /** 616 * Gets the user id. 617 * 618 * @return the user id 619 */ 620 public int getUserId() { 621 return uid; 622 } 623 624 /** 625 * Gets the tape volume where this file is located. 626 * 627 * @return the volume 628 */ 629 public int getVolume() { 630 return volume; 631 } 632 633 @Override 634 public int hashCode() { 635 return ino; 636 } 637 638 /** 639 * Is this a block device? 640 * 641 * @return whether this is a block device 642 */ 643 public boolean isBlkDev() { 644 return type == TYPE.BLKDEV; 645 } 646 647 /** 648 * Is this a character device? 649 * 650 * @return whether this is a character device 651 */ 652 public boolean isChrDev() { 653 return type == TYPE.CHRDEV; 654 } 655 656 /** 657 * Has this file been deleted? (On valid on incremental dumps.) 658 * 659 * @return whether the file has been deleted 660 */ 661 public boolean isDeleted() { 662 return isDeleted; 663 } 664 665 /** 666 * Is this a directory? 667 * 668 * @return whether this is a directory 669 */ 670 @Override 671 public boolean isDirectory() { 672 return type == TYPE.DIRECTORY; 673 } 674 675 /** 676 * Is this a fifo/pipe? 677 * 678 * @return whether this is a fifo 679 */ 680 public boolean isFifo() { 681 return type == TYPE.FIFO; 682 } 683 684 /** 685 * Is this a regular file? 686 * 687 * @return whether this is a regular file 688 */ 689 public boolean isFile() { 690 return type == TYPE.FILE; 691 } 692 693 /** 694 * Is this a network device? 695 * 696 * @return whether this is a socket 697 */ 698 public boolean isSocket() { 699 return type == TYPE.SOCKET; 700 } 701 702 /** 703 * Is this a sparse record? 704 * 705 * @param idx index of the record to check 706 * @return whether this is a sparse record 707 */ 708 public boolean isSparseRecord(final int idx) { 709 return (header.getCdata(idx) & 0x01) == 0; 710 } 711 712 /** 713 * Sets the time the file was last accessed. 714 * 715 * @param atime the access time 716 */ 717 public void setAccessTime(final Date atime) { 718 this.atime = atime.getTime(); 719 } 720 721 /** 722 * Sets the file creation time. 723 * 724 * @param ctime the creation time 725 */ 726 public void setCreationTime(final Date ctime) { 727 this.ctime = ctime.getTime(); 728 } 729 730 /** 731 * Sets whether this file has been deleted. 732 * 733 * @param isDeleted whether the file has been deleted 734 */ 735 public void setDeleted(final boolean isDeleted) { 736 this.isDeleted = isDeleted; 737 } 738 739 /** 740 * Sets the generation of the file. 741 * 742 * @param generation the generation 743 */ 744 public void setGeneration(final int generation) { 745 this.generation = generation; 746 } 747 748 /** 749 * Sets the group id. 750 * 751 * @param gid the group id 752 */ 753 public void setGroupId(final int gid) { 754 this.gid = gid; 755 } 756 757 /** 758 * Sets the time the file was last modified. 759 * 760 * @param mtime the last modified time 761 */ 762 public void setLastModifiedDate(final Date mtime) { 763 this.mtime = mtime.getTime(); 764 } 765 766 /** 767 * Sets the access permissions on the entry. 768 * 769 * @param mode the access permissions 770 */ 771 public void setMode(final int mode) { 772 this.mode = mode & 07777; 773 this.permissions = PERMISSION.find(mode); 774 } 775 776 /** 777 * Sets the name of the entry. 778 * 779 * @param name the name 780 */ 781 public final void setName(String name) { 782 this.originalName = name; 783 if (name != null) { 784 if (isDirectory() && !name.endsWith("/")) { 785 name += "/"; 786 } 787 if (name.startsWith("./")) { 788 name = name.substring(2); 789 } 790 } 791 this.name = name; 792 } 793 794 /** 795 * Sets the number of hard links. 796 * 797 * @param nlink the number of hard links 798 */ 799 public void setNlink(final int nlink) { 800 this.nlink = nlink; 801 } 802 803 /** 804 * Sets the offset within the archive. 805 * 806 * @param offset the offset 807 */ 808 public void setOffset(final long offset) { 809 this.offset = offset; 810 } 811 812 /** 813 * Sets the path of the entry. 814 * 815 * @param simpleName the simple name 816 */ 817 protected void setSimpleName(final String simpleName) { 818 this.simpleName = simpleName; 819 } 820 821 /** 822 * Sets the size of the entry. 823 * 824 * @param size the size 825 */ 826 public void setSize(final long size) { 827 this.size = size; 828 } 829 830 /** 831 * Sets the type of the entry. 832 * 833 * @param type the type 834 */ 835 public void setType(final TYPE type) { 836 this.type = type; 837 } 838 839 /** 840 * Sets the user id. 841 * 842 * @param uid the user id 843 */ 844 public void setUserId(final int uid) { 845 this.uid = uid; 846 } 847 848 /** 849 * Sets the tape volume. 850 * 851 * @param volume the volume 852 */ 853 public void setVolume(final int volume) { 854 this.volume = volume; 855 } 856 857 @Override 858 public String toString() { 859 return getName(); 860 } 861 862 /** 863 * Update entry with information from next tape segment header. 864 */ 865 void update(final byte[] buffer) { 866 header.volume = DumpArchiveUtil.convert32(buffer, 16); 867 header.count = DumpArchiveUtil.convert32(buffer, 160); 868 869 header.holes = 0; 870 871 for (int i = 0; i < 512 && i < header.count; i++) { 872 if (buffer[164 + i] == 0) { 873 header.holes++; 874 } 875 } 876 877 System.arraycopy(buffer, 164, header.cdata, 0, 512); 878 } 879 }