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