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 
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 }