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