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