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