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 }