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