    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    }