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     */
019    package org.apache.commons.compress.archivers.dump;
020    
021    import java.util.Collections;
022    import java.util.Date;
023    import java.util.EnumSet;
024    import java.util.HashSet;
025    import java.util.Set;
026    import 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     */
179    public 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 DumpArchiveSummary summary = null;
194    
195        // this information is available from standard index.
196        private 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 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    }