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 &lt;ufs/ufs/dinode.h&gt; 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 =&gt; data; 0 =&gt; 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}