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