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