View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.dump;
20  
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.EnumSet;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import org.apache.commons.compress.archivers.ArchiveEntry;
28  
29  /**
30   * This class represents an entry in a Dump archive. It consists of the entry's header, the entry's File and any extended attributes.
31   * <p>
32   * DumpEntries that are created from the header bytes read from an archive are instantiated with the DumpArchiveEntry( byte[] ) constructor. These entries will
33   * 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
34   * to null, since they reference an archive entry not a file.
35   * <p>
36   * 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
37   * 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
38   * and the File is set to null.
39   *
40   * <p>
41   * The C structure for a Dump Entry's header is:
42   *
43   * <pre>
44   * #define TP_BSIZE    1024          // size of each file block
45   * #define NTREC       10            // number of blocks to write at once
46   * #define HIGHDENSITYTREC 32        // number of blocks to write on high-density tapes
47   * #define TP_NINDIR   (TP_BSIZE/2)  // number if indirect inodes in record
48   * #define TP_NINOS    (TP_NINDIR / sizeof (int32_t))
49   * #define LBLSIZE     16
50   * #define NAMELEN     64
51   *
52   * #define OFS_MAGIC     (int) 60011  // old format magic value
53   * #define NFS_MAGIC     (int) 60012  // new format magic value
54   * #define FS_UFS2_MAGIC (int) 0x19540119
55   * #define CHECKSUM      (int) 84446  // constant used in checksum algorithm
56   *
57   * struct  s_spcl {
58   *   int32_t c_type;             // record type (see below)
59   *   int32_t <b>c_date</b>;             // date of this dump
60   *   int32_t <b>c_ddate</b>;            // date of previous dump
61   *   int32_t c_volume;           // dump volume number
62   *   u_int32_t c_tapea;          // logical block of this record
63   *   dump_ino_t c_ino;           // number of inode
64   *   int32_t <b>c_magic</b>;            // magic number (see above)
65   *   int32_t c_checksum;         // record checksum
66   * #ifdef  __linux__
67   *   struct  new_bsd_inode c_dinode;
68   * #else
69   * #ifdef sunos
70   *   struct  new_bsd_inode c_dinode;
71   * #else
72   *   struct  dinode  c_dinode;   // ownership and mode of inode
73   * #endif
74   * #endif
75   *   int32_t c_count;            // number of valid c_addr entries
76   *   union u_data c_data;        // see above
77   *   char    <b>c_label[LBLSIZE]</b>;   // dump label
78   *   int32_t <b>c_level</b>;            // level of this dump
79   *   char    <b>c_filesys[NAMELEN]</b>; // name of dumpped file system
80   *   char    <b>c_dev[NAMELEN]</b>;     // name of dumpped device
81   *   char    <b>c_host[NAMELEN]</b>;    // name of dumpped host
82   *   int32_t c_flags;            // additional information (see below)
83   *   int32_t c_firstrec;         // first record on volume
84   *   int32_t c_ntrec;            // blocksize on volume
85   *   int32_t c_extattributes;    // additional inode info (see below)
86   *   int32_t c_spare[30];        // reserved for future uses
87   * } s_spcl;
88   *
89   * //
90   * // flag values
91   * //
92   * #define DR_NEWHEADER     0x0001  // new format tape header
93   * #define DR_NEWINODEFMT   0x0002  // new format inodes on tape
94   * #define DR_COMPRESSED    0x0080  // dump tape is compressed
95   * #define DR_METAONLY      0x0100  // only the metadata of the inode has been dumped
96   * #define DR_INODEINFO     0x0002  // [SIC] TS_END header contains c_inos information
97   * #define DR_EXTATTRIBUTES 0x8000
98   *
99   * //
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  */
174 public 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 
346     /**
347      * This value is available from the standard index.
348      */
349     private final TapeSegmentHeader header = new TapeSegmentHeader();
350     private String simpleName;
351     private String originalName;
352 
353     /**
354      * This value is available from the QFA index.
355      */
356     private int volume;
357     private long offset;
358     private int ino;
359 
360     private int nlink;
361 
362     private long ctime;
363 
364     private int generation;
365 
366     private boolean isDeleted;
367 
368     /**
369      * Constructs a default instance.
370      */
371     public DumpArchiveEntry() {
372     }
373 
374     /**
375      * Constructs a new instance with only names.
376      *
377      * @param name       path name
378      * @param simpleName actual file name.
379      */
380     public DumpArchiveEntry(final String name, final String simpleName) {
381         setName(name);
382         this.simpleName = simpleName;
383     }
384 
385     /**
386      * Constructs a new instance with name, inode and type.
387      *
388      * @param name       the name
389      * @param simpleName the simple name
390      * @param ino        the ino
391      * @param type       the type
392      */
393     protected DumpArchiveEntry(final String name, final String simpleName, final int ino, final TYPE type) {
394         setType(type);
395         setName(name);
396         this.simpleName = simpleName;
397         this.ino = ino;
398         this.offset = 0;
399     }
400 
401     @Override
402     public boolean equals(final Object o) {
403         if (o == this) {
404             return true;
405         }
406         if (o == null || !o.getClass().equals(getClass())) {
407             return false;
408         }
409 
410         final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
411 
412         if (ino != rhs.ino) {
413             return false;
414         }
415 
416         // summary is always null right now, but this may change some day
417         if (summary == null && rhs.summary != null // NOSONAR
418                 || summary != null && !summary.equals(rhs.summary)) { // NOSONAR
419             return false;
420         }
421 
422         return true;
423     }
424 
425     /**
426      * Returns the time the file was last accessed.
427      *
428      * @return the access time
429      */
430     public Date getAccessTime() {
431         return new Date(atime);
432     }
433 
434     /**
435      * Gets file creation time.
436      *
437      * @return the creation time
438      */
439     public Date getCreationTime() {
440         return new Date(ctime);
441     }
442 
443     /**
444      * Returns the size of the entry as read from the archive.
445      */
446     long getEntrySize() {
447         return size;
448     }
449 
450     /**
451      * Gets the generation of the file.
452      *
453      * @return the generation
454      */
455     public int getGeneration() {
456         return generation;
457     }
458 
459     /**
460      * Gets the group id
461      *
462      * @return the group id
463      */
464     public int getGroupId() {
465         return gid;
466     }
467 
468     /**
469      * Gets the number of records in this segment.
470      *
471      * @return the number of records
472      */
473     public int getHeaderCount() {
474         return header.getCount();
475     }
476 
477     /**
478      * Gets the number of sparse records in this segment.
479      *
480      * @return the number of sparse records
481      */
482     public int getHeaderHoles() {
483         return header.getHoles();
484     }
485 
486     /**
487      * Gets the type of the tape segment header.
488      *
489      * @return the segment header
490      */
491     public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() {
492         return header.getType();
493     }
494 
495     /**
496      * Returns the ino of the entry.
497      *
498      * @return the ino
499      */
500     public int getIno() {
501         return header.getIno();
502     }
503 
504     /**
505      * The last modified date.
506      *
507      * @return the last modified date
508      */
509     @Override
510     public Date getLastModifiedDate() {
511         return new Date(mtime);
512     }
513 
514     /**
515      * Gets the access permissions on the entry.
516      *
517      * @return the access permissions
518      */
519     public int getMode() {
520         return mode;
521     }
522 
523     /**
524      * Returns the name of the entry.
525      *
526      * <p>
527      * This method returns the raw name as it is stored inside of the archive.
528      * </p>
529      *
530      * @return the name of the entry.
531      */
532     @Override
533     public String getName() {
534         return name;
535     }
536 
537     /**
538      * Gets the number of hard links to the entry.
539      *
540      * @return the number of hard links
541      */
542     public int getNlink() {
543         return nlink;
544     }
545 
546     /**
547      * Gets the offset within the archive
548      *
549      * @return the offset
550      */
551     public long getOffset() {
552         return offset;
553     }
554 
555     /**
556      * Returns the unmodified name of the entry.
557      *
558      * @return the name of the entry.
559      */
560     String getOriginalName() {
561         return originalName;
562     }
563 
564     /**
565      * Returns the permissions on the entry.
566      *
567      * @return the permissions
568      */
569     public Set<PERMISSION> getPermissions() {
570         return permissions;
571     }
572 
573     /**
574      * Returns the path of the entry.
575      *
576      * @return the path of the entry.
577      */
578     public String getSimpleName() {
579         return simpleName;
580     }
581 
582     /**
583      * Returns the size of the entry.
584      *
585      * @return the size
586      */
587     @Override
588     public long getSize() {
589         return isDirectory() ? SIZE_UNKNOWN : size;
590     }
591 
592     /**
593      * Gets the type of the entry.
594      *
595      * @return the type
596      */
597     public TYPE getType() {
598         return type;
599     }
600 
601     /**
602      * Gets the user id.
603      *
604      * @return the user id
605      */
606     public int getUserId() {
607         return uid;
608     }
609 
610     /**
611      * Gets the tape volume where this file is located.
612      *
613      * @return the volume
614      */
615     public int getVolume() {
616         return volume;
617     }
618 
619     @Override
620     public int hashCode() {
621         return ino;
622     }
623 
624     /**
625      * Is this a block device?
626      *
627      * @return whether this is a block device
628      */
629     public boolean isBlkDev() {
630         return type == TYPE.BLKDEV;
631     }
632 
633     /**
634      * Is this a character device?
635      *
636      * @return whether this is a character device
637      */
638     public boolean isChrDev() {
639         return type == TYPE.CHRDEV;
640     }
641 
642     /**
643      * Has this file been deleted? (On valid on incremental dumps.)
644      *
645      * @return whether the file has been deleted
646      */
647     public boolean isDeleted() {
648         return isDeleted;
649     }
650 
651     /**
652      * Is this a directory?
653      *
654      * @return whether this is a directory
655      */
656     @Override
657     public boolean isDirectory() {
658         return type == TYPE.DIRECTORY;
659     }
660 
661     /**
662      * Is this a fifo/pipe?
663      *
664      * @return whether this is a fifo
665      */
666     public boolean isFifo() {
667         return type == TYPE.FIFO;
668     }
669 
670     /**
671      * Is this a regular file?
672      *
673      * @return whether this is a regular file
674      */
675     public boolean isFile() {
676         return type == TYPE.FILE;
677     }
678 
679     /**
680      * Is this a network device?
681      *
682      * @return whether this is a socket
683      */
684     public boolean isSocket() {
685         return type == TYPE.SOCKET;
686     }
687 
688     /**
689      * Is this a sparse record?
690      *
691      * @param idx index of the record to check
692      * @return whether this is a sparse record
693      */
694     public boolean isSparseRecord(final int idx) {
695         return (header.getCdata(idx) & 0x01) == 0;
696     }
697 
698     /**
699      * Sets the time the file was last accessed.
700      *
701      * @param atime the access time
702      */
703     public void setAccessTime(final Date atime) {
704         this.atime = atime.getTime();
705     }
706 
707     /**
708      * Sets the file creation time.
709      *
710      * @param ctime the creation time
711      */
712     public void setCreationTime(final Date ctime) {
713         this.ctime = ctime.getTime();
714     }
715 
716     /**
717      * Sets whether this file has been deleted.
718      *
719      * @param isDeleted whether the file has been deleted
720      */
721     public void setDeleted(final boolean isDeleted) {
722         this.isDeleted = isDeleted;
723     }
724 
725     /**
726      * Sets the generation of the file.
727      *
728      * @param generation the generation
729      */
730     public void setGeneration(final int generation) {
731         this.generation = generation;
732     }
733 
734     /**
735      * Sets the group id.
736      *
737      * @param gid the group id
738      */
739     public void setGroupId(final int gid) {
740         this.gid = gid;
741     }
742 
743     /**
744      * Sets the time the file was last modified.
745      *
746      * @param mtime the last modified time
747      */
748     public void setLastModifiedDate(final Date mtime) {
749         this.mtime = mtime.getTime();
750     }
751 
752     /**
753      * Sets the access permissions on the entry.
754      *
755      * @param mode the access permissions
756      */
757     public void setMode(final int mode) {
758         this.mode = mode & 07777;
759         this.permissions = PERMISSION.find(mode);
760     }
761 
762     /**
763      * Sets the name of the entry.
764      *
765      * @param name the name
766      */
767     public final void setName(String name) {
768         this.originalName = name;
769         if (name != null) {
770             if (isDirectory() && !name.endsWith("/")) {
771                 name += "/";
772             }
773             if (name.startsWith("./")) {
774                 name = name.substring(2);
775             }
776         }
777         this.name = name;
778     }
779 
780     /**
781      * Sets the number of hard links.
782      *
783      * @param nlink the number of hard links
784      */
785     public void setNlink(final int nlink) {
786         this.nlink = nlink;
787     }
788 
789     /**
790      * Sets the offset within the archive.
791      *
792      * @param offset the offset
793      */
794     public void setOffset(final long offset) {
795         this.offset = offset;
796     }
797 
798     /**
799      * Sets the path of the entry.
800      *
801      * @param simpleName the simple name
802      */
803     protected void setSimpleName(final String simpleName) {
804         this.simpleName = simpleName;
805     }
806 
807     /**
808      * Sets the size of the entry.
809      *
810      * @param size the size
811      */
812     public void setSize(final long size) {
813         this.size = size;
814     }
815 
816     /**
817      * Sets the type of the entry.
818      *
819      * @param type the type
820      */
821     public void setType(final TYPE type) {
822         this.type = type;
823     }
824 
825     /**
826      * Sets the user id.
827      *
828      * @param uid the user id
829      */
830     public void setUserId(final int uid) {
831         this.uid = uid;
832     }
833 
834     /**
835      * Sets the tape volume.
836      *
837      * @param volume the volume
838      */
839     public void setVolume(final int volume) {
840         this.volume = volume;
841     }
842 
843     @Override
844     public String toString() {
845         return getName();
846     }
847 
848     /**
849      * Update entry with information from next tape segment header.
850      */
851     void update(final byte[] buffer) {
852         header.volume = DumpArchiveUtil.convert32(buffer, 16);
853         header.count = DumpArchiveUtil.convert32(buffer, 160);
854 
855         header.holes = 0;
856 
857         for (int i = 0; i < 512 && i < header.count; i++) {
858             if (buffer[164 + i] == 0) {
859                 header.holes++;
860             }
861         }
862 
863         System.arraycopy(buffer, 164, header.cdata, 0, 512);
864     }
865 }