DumpArchiveEntry.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- package org.apache.commons.compress.archivers.dump;
- import java.util.Collections;
- import java.util.Date;
- import java.util.EnumSet;
- import java.util.HashSet;
- import java.util.Set;
- import org.apache.commons.compress.archivers.ArchiveEntry;
- /**
- * This class represents an entry in a Dump archive. It consists of the entry's header, the entry's File and any extended attributes.
- * <p>
- * DumpEntries that are created from the header bytes read from an archive are instantiated with the DumpArchiveEntry( byte[] ) constructor. These entries will
- * 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
- * to null, since they reference an archive entry not a file.
- * <p>
- * 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
- * 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
- * and the File is set to null.
- *
- * <p>
- * The C structure for a Dump Entry's header is:
- *
- * <pre>
- * #define TP_BSIZE 1024 // size of each file block
- * #define NTREC 10 // number of blocks to write at once
- * #define HIGHDENSITYTREC 32 // number of blocks to write on high-density tapes
- * #define TP_NINDIR (TP_BSIZE/2) // number if indirect inodes in record
- * #define TP_NINOS (TP_NINDIR / sizeof (int32_t))
- * #define LBLSIZE 16
- * #define NAMELEN 64
- *
- * #define OFS_MAGIC (int) 60011 // old format magic value
- * #define NFS_MAGIC (int) 60012 // new format magic value
- * #define FS_UFS2_MAGIC (int) 0x19540119
- * #define CHECKSUM (int) 84446 // constant used in checksum algorithm
- *
- * struct s_spcl {
- * int32_t c_type; // record type (see below)
- * int32_t <b>c_date</b>; // date of this dump
- * int32_t <b>c_ddate</b>; // date of previous dump
- * int32_t c_volume; // dump volume number
- * u_int32_t c_tapea; // logical block of this record
- * dump_ino_t c_ino; // number of inode
- * int32_t <b>c_magic</b>; // magic number (see above)
- * int32_t c_checksum; // record checksum
- * #ifdef __linux__
- * struct new_bsd_inode c_dinode;
- * #else
- * #ifdef sunos
- * struct new_bsd_inode c_dinode;
- * #else
- * struct dinode c_dinode; // ownership and mode of inode
- * #endif
- * #endif
- * int32_t c_count; // number of valid c_addr entries
- * union u_data c_data; // see above
- * char <b>c_label[LBLSIZE]</b>; // dump label
- * int32_t <b>c_level</b>; // level of this dump
- * char <b>c_filesys[NAMELEN]</b>; // name of dumpped file system
- * char <b>c_dev[NAMELEN]</b>; // name of dumpped device
- * char <b>c_host[NAMELEN]</b>; // name of dumpped host
- * int32_t c_flags; // additional information (see below)
- * int32_t c_firstrec; // first record on volume
- * int32_t c_ntrec; // blocksize on volume
- * int32_t c_extattributes; // additional inode info (see below)
- * int32_t c_spare[30]; // reserved for future uses
- * } s_spcl;
- *
- * //
- * // flag values
- * //
- * #define DR_NEWHEADER 0x0001 // new format tape header
- * #define DR_NEWINODEFMT 0x0002 // new format inodes on tape
- * #define DR_COMPRESSED 0x0080 // dump tape is compressed
- * #define DR_METAONLY 0x0100 // only the metadata of the inode has been dumped
- * #define DR_INODEINFO 0x0002 // [SIC] TS_END header contains c_inos information
- * #define DR_EXTATTRIBUTES 0x8000
- *
- * //
- * // extattributes inode info
- * //
- * #define EXT_REGULAR 0
- * #define EXT_MACOSFNDRINFO 1
- * #define EXT_MACOSRESFORK 2
- * #define EXT_XATTR 3
- *
- * // used for EA on tape
- * #define EXT2_GOOD_OLD_INODE_SIZE 128
- * #define EXT2_XATTR_MAGIC 0xEA020000 // block EA
- * #define EXT2_XATTR_MAGIC2 0xEA020001 // in inode EA
- * </pre>
- * <p>
- * The fields in <b>bold</b> are the same for all blocks. (This permitted multiple dumps to be written to a single tape.)
- * </p>
- *
- * <p>
- * The C structure for the inode (file) information is:
- *
- * <pre>
- * struct bsdtimeval { // **** alpha-*-linux is deviant
- * __u32 tv_sec;
- * __u32 tv_usec;
- * };
- *
- * #define NDADDR 12
- * #define NIADDR 3
- *
- * //
- * // This is the new (4.4) BSD inode structure
- * // copied from the FreeBSD 2.0 <ufs/ufs/dinode.h> include file
- * //
- * struct new_bsd_inode {
- * __u16 di_mode; // file type, standard UNIX permissions
- * __s16 di_nlink; // number of hard links to file.
- * union {
- * __u16 oldids[2];
- * __u32 inumber;
- * } di_u;
- * u_quad_t di_size; // file size
- * struct bsdtimeval di_atime; // time file was last accessed
- * struct bsdtimeval di_mtime; // time file was last modified
- * struct bsdtimeval di_ctime; // time file was created
- * __u32 di_db[NDADDR];
- * __u32 di_ib[NIADDR];
- * __u32 di_flags; //
- * __s32 di_blocks; // number of disk blocks
- * __s32 di_gen; // generation number
- * __u32 di_uid; // user id (see /etc/passwd)
- * __u32 di_gid; // group id (see /etc/group)
- * __s32 di_spare[2]; // unused
- * };
- * </pre>
- * <p>
- * 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
- * physical file. You must read the contents of the directory entries to learn the mapping(s) from file name to inode.
- * </p>
- *
- * <p>
- * 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:
- * </p>
- *
- * <pre>
- * #define TP_BSIZE 1024
- * #define TP_NINDIR (TP_BSIZE/2)
- *
- * union u_data {
- * char s_addrs[TP_NINDIR]; // 1 => data; 0 => hole in inode
- * int32_t s_inos[TP_NINOS]; // table of first inode on each volume
- * } u_data;
- * </pre>
- *
- * @NotThreadSafe
- */
- public class DumpArchiveEntry implements ArchiveEntry {
- public enum PERMISSION {
- // Note: The arguments are octal values
- // @formatter:off
- SETUID(04000),
- SETGUI(02000),
- STICKY(01000),
- USER_READ(00400),
- USER_WRITE(00200),
- USER_EXEC(00100),
- GROUP_READ(00040),
- GROUP_WRITE(00020),
- GROUP_EXEC(00010),
- WORLD_READ(00004),
- WORLD_WRITE(00002),
- WORLD_EXEC(00001);
- // @formatter:on
- public static Set<PERMISSION> find(final int code) {
- final Set<PERMISSION> set = new HashSet<>();
- for (final PERMISSION p : values()) {
- if ((code & p.code) == p.code) {
- set.add(p);
- }
- }
- if (set.isEmpty()) {
- return Collections.emptySet();
- }
- return EnumSet.copyOf(set);
- }
- private final int code;
- PERMISSION(final int code) {
- this.code = code;
- }
- }
- /**
- * Archive entry as stored on tape. There is one TSH for (at most) every 512k in the file.
- */
- static class TapeSegmentHeader {
- private DumpArchiveConstants.SEGMENT_TYPE type;
- private int volume;
- private int ino;
- private int count;
- private int holes;
- private final byte[] cdata = new byte[512]; // map of any 'holes'
- public int getCdata(final int idx) {
- return cdata[idx];
- }
- public int getCount() {
- return count;
- }
- public int getHoles() {
- return holes;
- }
- public int getIno() {
- return ino;
- }
- public DumpArchiveConstants.SEGMENT_TYPE getType() {
- return type;
- }
- public int getVolume() {
- return volume;
- }
- void setIno(final int ino) {
- this.ino = ino;
- }
- }
- public enum TYPE {
- WHITEOUT(14), SOCKET(12), LINK(10), FILE(8), BLKDEV(6), DIRECTORY(4), CHRDEV(2), FIFO(1), UNKNOWN(15);
- public static TYPE find(final int code) {
- TYPE type = UNKNOWN;
- for (final TYPE t : values()) {
- if (code == t.code) {
- type = t;
- }
- }
- return type;
- }
- private final int code;
- TYPE(final int code) {
- this.code = code;
- }
- }
- /**
- * Populate the dump archive entry and tape segment header with the contents of the buffer.
- *
- * @param buffer buffer to read content from
- */
- static DumpArchiveEntry parse(final byte[] buffer) {
- final DumpArchiveEntry entry = new DumpArchiveEntry();
- final TapeSegmentHeader header = entry.header;
- header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(buffer, 0));
- // header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4));
- // header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32(
- // buffer, 8));
- header.volume = DumpArchiveUtil.convert32(buffer, 12);
- // header.tapea = DumpArchiveUtil.convert32(buffer, 16);
- entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20);
- // header.magic = DumpArchiveUtil.convert32(buffer, 24);
- // header.checksum = DumpArchiveUtil.convert32(buffer, 28);
- final int m = DumpArchiveUtil.convert16(buffer, 32);
- // determine the type of the file.
- entry.setType(TYPE.find(m >> 12 & 0x0F));
- // determine the standard permissions
- entry.setMode(m);
- entry.nlink = DumpArchiveUtil.convert16(buffer, 34);
- // inumber, oldids?
- entry.setSize(DumpArchiveUtil.convert64(buffer, 40));
- long t = 1000L * DumpArchiveUtil.convert32(buffer, 48) + DumpArchiveUtil.convert32(buffer, 52) / 1000;
- entry.setAccessTime(new Date(t));
- t = 1000L * DumpArchiveUtil.convert32(buffer, 56) + DumpArchiveUtil.convert32(buffer, 60) / 1000;
- entry.setLastModifiedDate(new Date(t));
- t = 1000L * DumpArchiveUtil.convert32(buffer, 64) + DumpArchiveUtil.convert32(buffer, 68) / 1000;
- entry.ctime = t;
- // db: 72-119 - direct blocks
- // id: 120-131 - indirect blocks
- // entry.flags = DumpArchiveUtil.convert32(buffer, 132);
- // entry.blocks = DumpArchiveUtil.convert32(buffer, 136);
- entry.generation = DumpArchiveUtil.convert32(buffer, 140);
- entry.setUserId(DumpArchiveUtil.convert32(buffer, 144));
- entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148));
- // two 32-bit spare values.
- header.count = DumpArchiveUtil.convert32(buffer, 160);
- header.holes = 0;
- for (int i = 0; i < 512 && i < header.count; i++) {
- if (buffer[164 + i] == 0) {
- header.holes++;
- }
- }
- System.arraycopy(buffer, 164, header.cdata, 0, 512);
- entry.volume = header.getVolume();
- // entry.isSummaryOnly = false;
- return entry;
- }
- private String name;
- private TYPE type = TYPE.UNKNOWN;
- private int mode;
- private Set<PERMISSION> permissions = Collections.emptySet();
- private long size;
- private long atime;
- private long mtime;
- private int uid;
- private int gid;
- /**
- * Currently unused
- */
- private final DumpArchiveSummary summary = null;
- /**
- * This value is available from the standard index.
- */
- private final TapeSegmentHeader header = new TapeSegmentHeader();
- private String simpleName;
- private String originalName;
- /**
- * This value is available from the QFA index.
- */
- private int volume;
- private long offset;
- private int ino;
- private int nlink;
- private long ctime;
- private int generation;
- private boolean isDeleted;
- /**
- * Constructs a default instance.
- */
- public DumpArchiveEntry() {
- }
- /**
- * Constructs a new instance with only names.
- *
- * @param name path name
- * @param simpleName actual file name.
- */
- public DumpArchiveEntry(final String name, final String simpleName) {
- setName(name);
- this.simpleName = simpleName;
- }
- /**
- * Constructs a new instance with name, inode and type.
- *
- * @param name the name
- * @param simpleName the simple name
- * @param ino the ino
- * @param type the type
- */
- protected DumpArchiveEntry(final String name, final String simpleName, final int ino, final TYPE type) {
- setType(type);
- setName(name);
- this.simpleName = simpleName;
- this.ino = ino;
- this.offset = 0;
- }
- @Override
- public boolean equals(final Object o) {
- if (o == this) {
- return true;
- }
- if (o == null || !o.getClass().equals(getClass())) {
- return false;
- }
- final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
- if (ino != rhs.ino) {
- return false;
- }
- // summary is always null right now, but this may change some day
- if (summary == null && rhs.summary != null // NOSONAR
- || summary != null && !summary.equals(rhs.summary)) { // NOSONAR
- return false;
- }
- return true;
- }
- /**
- * Returns the time the file was last accessed.
- *
- * @return the access time
- */
- public Date getAccessTime() {
- return new Date(atime);
- }
- /**
- * Gets file creation time.
- *
- * @return the creation time
- */
- public Date getCreationTime() {
- return new Date(ctime);
- }
- /**
- * Returns the size of the entry as read from the archive.
- */
- long getEntrySize() {
- return size;
- }
- /**
- * Gets the generation of the file.
- *
- * @return the generation
- */
- public int getGeneration() {
- return generation;
- }
- /**
- * Gets the group id
- *
- * @return the group id
- */
- public int getGroupId() {
- return gid;
- }
- /**
- * Gets the number of records in this segment.
- *
- * @return the number of records
- */
- public int getHeaderCount() {
- return header.getCount();
- }
- /**
- * Gets the number of sparse records in this segment.
- *
- * @return the number of sparse records
- */
- public int getHeaderHoles() {
- return header.getHoles();
- }
- /**
- * Gets the type of the tape segment header.
- *
- * @return the segment header
- */
- public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() {
- return header.getType();
- }
- /**
- * Returns the ino of the entry.
- *
- * @return the ino
- */
- public int getIno() {
- return header.getIno();
- }
- /**
- * The last modified date.
- *
- * @return the last modified date
- */
- @Override
- public Date getLastModifiedDate() {
- return new Date(mtime);
- }
- /**
- * Gets the access permissions on the entry.
- *
- * @return the access permissions
- */
- public int getMode() {
- return mode;
- }
- /**
- * Returns the name of the entry.
- *
- * <p>
- * This method returns the raw name as it is stored inside of the archive.
- * </p>
- *
- * @return the name of the entry.
- */
- @Override
- public String getName() {
- return name;
- }
- /**
- * Gets the number of hard links to the entry.
- *
- * @return the number of hard links
- */
- public int getNlink() {
- return nlink;
- }
- /**
- * Gets the offset within the archive
- *
- * @return the offset
- */
- public long getOffset() {
- return offset;
- }
- /**
- * Returns the unmodified name of the entry.
- *
- * @return the name of the entry.
- */
- String getOriginalName() {
- return originalName;
- }
- /**
- * Returns the permissions on the entry.
- *
- * @return the permissions
- */
- public Set<PERMISSION> getPermissions() {
- return permissions;
- }
- /**
- * Returns the path of the entry.
- *
- * @return the path of the entry.
- */
- public String getSimpleName() {
- return simpleName;
- }
- /**
- * Returns the size of the entry.
- *
- * @return the size
- */
- @Override
- public long getSize() {
- return isDirectory() ? SIZE_UNKNOWN : size;
- }
- /**
- * Gets the type of the entry.
- *
- * @return the type
- */
- public TYPE getType() {
- return type;
- }
- /**
- * Gets the user id.
- *
- * @return the user id
- */
- public int getUserId() {
- return uid;
- }
- /**
- * Gets the tape volume where this file is located.
- *
- * @return the volume
- */
- public int getVolume() {
- return volume;
- }
- @Override
- public int hashCode() {
- return ino;
- }
- /**
- * Is this a block device?
- *
- * @return whether this is a block device
- */
- public boolean isBlkDev() {
- return type == TYPE.BLKDEV;
- }
- /**
- * Is this a character device?
- *
- * @return whether this is a character device
- */
- public boolean isChrDev() {
- return type == TYPE.CHRDEV;
- }
- /**
- * Has this file been deleted? (On valid on incremental dumps.)
- *
- * @return whether the file has been deleted
- */
- public boolean isDeleted() {
- return isDeleted;
- }
- /**
- * Is this a directory?
- *
- * @return whether this is a directory
- */
- @Override
- public boolean isDirectory() {
- return type == TYPE.DIRECTORY;
- }
- /**
- * Is this a fifo/pipe?
- *
- * @return whether this is a fifo
- */
- public boolean isFifo() {
- return type == TYPE.FIFO;
- }
- /**
- * Is this a regular file?
- *
- * @return whether this is a regular file
- */
- public boolean isFile() {
- return type == TYPE.FILE;
- }
- /**
- * Is this a network device?
- *
- * @return whether this is a socket
- */
- public boolean isSocket() {
- return type == TYPE.SOCKET;
- }
- /**
- * Is this a sparse record?
- *
- * @param idx index of the record to check
- * @return whether this is a sparse record
- */
- public boolean isSparseRecord(final int idx) {
- return (header.getCdata(idx) & 0x01) == 0;
- }
- /**
- * Sets the time the file was last accessed.
- *
- * @param atime the access time
- */
- public void setAccessTime(final Date atime) {
- this.atime = atime.getTime();
- }
- /**
- * Sets the file creation time.
- *
- * @param ctime the creation time
- */
- public void setCreationTime(final Date ctime) {
- this.ctime = ctime.getTime();
- }
- /**
- * Sets whether this file has been deleted.
- *
- * @param isDeleted whether the file has been deleted
- */
- public void setDeleted(final boolean isDeleted) {
- this.isDeleted = isDeleted;
- }
- /**
- * Sets the generation of the file.
- *
- * @param generation the generation
- */
- public void setGeneration(final int generation) {
- this.generation = generation;
- }
- /**
- * Sets the group id.
- *
- * @param gid the group id
- */
- public void setGroupId(final int gid) {
- this.gid = gid;
- }
- /**
- * Sets the time the file was last modified.
- *
- * @param mtime the last modified time
- */
- public void setLastModifiedDate(final Date mtime) {
- this.mtime = mtime.getTime();
- }
- /**
- * Sets the access permissions on the entry.
- *
- * @param mode the access permissions
- */
- public void setMode(final int mode) {
- this.mode = mode & 07777;
- this.permissions = PERMISSION.find(mode);
- }
- /**
- * Sets the name of the entry.
- *
- * @param name the name
- */
- public final void setName(String name) {
- this.originalName = name;
- if (name != null) {
- if (isDirectory() && !name.endsWith("/")) {
- name += "/";
- }
- if (name.startsWith("./")) {
- name = name.substring(2);
- }
- }
- this.name = name;
- }
- /**
- * Sets the number of hard links.
- *
- * @param nlink the number of hard links
- */
- public void setNlink(final int nlink) {
- this.nlink = nlink;
- }
- /**
- * Sets the offset within the archive.
- *
- * @param offset the offset
- */
- public void setOffset(final long offset) {
- this.offset = offset;
- }
- /**
- * Sets the path of the entry.
- *
- * @param simpleName the simple name
- */
- protected void setSimpleName(final String simpleName) {
- this.simpleName = simpleName;
- }
- /**
- * Sets the size of the entry.
- *
- * @param size the size
- */
- public void setSize(final long size) {
- this.size = size;
- }
- /**
- * Sets the type of the entry.
- *
- * @param type the type
- */
- public void setType(final TYPE type) {
- this.type = type;
- }
- /**
- * Sets the user id.
- *
- * @param uid the user id
- */
- public void setUserId(final int uid) {
- this.uid = uid;
- }
- /**
- * Sets the tape volume.
- *
- * @param volume the volume
- */
- public void setVolume(final int volume) {
- this.volume = volume;
- }
- @Override
- public String toString() {
- return getName();
- }
- /**
- * Update entry with information from next tape segment header.
- */
- void update(final byte[] buffer) {
- header.volume = DumpArchiveUtil.convert32(buffer, 16);
- header.count = DumpArchiveUtil.convert32(buffer, 160);
- header.holes = 0;
- for (int i = 0; i < 512 && i < header.count; i++) {
- if (buffer[164 + i] == 0) {
- header.holes++;
- }
- }
- System.arraycopy(buffer, 164, header.cdata, 0, 512);
- }
- }