    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 */
    019package org.apache.commons.compress.archivers.dump;
    020
    021import java.util.Collections;
    022import java.util.Date;
    023import java.util.EnumSet;
    024import java.util.HashSet;
    025import java.util.Set;
    026import 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 * <p>
    118 * The fields in <b>bold</b> are the same for all blocks. (This permitted
    119 * multiple dumps to be written to a single tape.)
    120 * </p>
    121 *
    122 * <p>
    123 * The C structure for the inode (file) information is:
    124 * <pre>
    125 * struct bsdtimeval {           //  **** alpha-*-linux is deviant
    126 *   __u32   tv_sec;
    127 *   __u32   tv_usec;
    128 * };
    129 *
    130 * #define NDADDR      12
    131 * #define NIADDR       3
    132 *
    133 * //
    134 * // This is the new (4.4) BSD inode structure
    135 * // copied from the FreeBSD 2.0 &lt;ufs/ufs/dinode.h&gt; include file
    136 * //
    137 * struct new_bsd_inode {
    138 *   __u16       di_mode;           // file type, standard Unix permissions
    139 *   __s16       di_nlink;          // number of hard links to file.
    140 *   union {
    141 *      __u16       oldids[2];
    142 *      __u32       inumber;
    143 *   }           di_u;
    144 *   u_quad_t    di_size;           // file size
    145 *   struct bsdtimeval   di_atime;  // time file was last accessed
    146 *   struct bsdtimeval   di_mtime;  // time file was last modified
    147 *   struct bsdtimeval   di_ctime;  // time file was created
    148 *   __u32       di_db[NDADDR];
    149 *   __u32       di_ib[NIADDR];
    150 *   __u32       di_flags;          //
    151 *   __s32       di_blocks;         // number of disk blocks
    152 *   __s32       di_gen;            // generation number
    153 *   __u32       di_uid;            // user id (see /etc/passwd)
    154 *   __u32       di_gid;            // group id (see /etc/group)
    155 *   __s32       di_spare[2];       // unused
    156 * };
    157 * </pre>
    158 * <p>
    159 * It is important to note that the header DOES NOT have the name of the
    160 * file. It can't since hard links mean that you may have multiple file names
    161 * for a single physical file. You must read the contents of the directory
    162 * entries to learn the mapping(s) from file name to inode.
    163 * </p>
    164 *
    165 * <p>
    166 * The C structure that indicates if a specific block is a real block
    167 * that contains data or is a sparse block that is not persisted to the
    168 * disk is:</p>
    169 * <pre>
    170 * #define TP_BSIZE    1024
    171 * #define TP_NINDIR   (TP_BSIZE/2)
    172 *
    173 * union u_data {
    174 *   char    s_addrs[TP_NINDIR]; // 1 =&gt; data; 0 =&gt; hole in inode
    175 *   int32_t s_inos[TP_NINOS];   // table of first inode on each volume
    176 * } u_data;
    177 * </pre>
    178 *
    179 * @NotThreadSafe
    180 */
    181public class DumpArchiveEntry implements ArchiveEntry {
    182    private String name;
    183    private TYPE type = TYPE.UNKNOWN;
    184    private int mode;
    185    private Set<PERMISSION> permissions = Collections.emptySet();
    186    private long size;
    187    private long atime;
    188    private long mtime;
    189    private int uid;
    190    private int gid;
    191
    192    /**
    193     * Currently unused
    194     */
    195    private final DumpArchiveSummary summary = null;
    196
    197    // this information is available from standard index.
    198    private final TapeSegmentHeader header = new TapeSegmentHeader();
    199    private String simpleName;
    200    private String originalName;
    201
    202    // this information is available from QFA index
    203    private int volume;
    204    private long offset;
    205    private int ino;
    206    private int nlink;
    207    private long ctime;
    208    private int generation;
    209    private boolean isDeleted;
    210
    211    /**
    212     * Default constructor.
    213     */
    214    public DumpArchiveEntry() {
    215    }
    216
    217    /**
    218     * Constructor taking only file name.
    219     * @param name pathname
    220     * @param simpleName actual file name.
    221     */
    222    public DumpArchiveEntry(final String name, final String simpleName) {
    223        setName(name);
    224        this.simpleName = simpleName;
    225    }
    226
    227    /**
    228     * Constructor taking name, inode and type.
    229     *
    230     * @param name the name
    231     * @param simpleName the simple name
    232     * @param ino the ino
    233     * @param type the type
    234     */
    235    protected DumpArchiveEntry(final String name, final String simpleName, final int ino,
    236                               final TYPE type) {
    237        setType(type);
    238        setName(name);
    239        this.simpleName = simpleName;
    240        this.ino = ino;
    241        this.offset = 0;
    242    }
    243
    244    /**
    245     * Returns the path of the entry.
    246     * @return the path of the entry.
    247     */
    248    public String getSimpleName() {
    249        return simpleName;
    250    }
    251
    252    /**
    253     * Sets the path of the entry.
    254     * @param simpleName the simple name
    255     */
    256    protected void setSimpleName(final String simpleName) {
    257        this.simpleName = simpleName;
    258    }
    259
    260    /**
    261     * Returns the ino of the entry.
    262     * @return the ino
    263     */
    264    public int getIno() {
    265        return header.getIno();
    266    }
    267
    268    /**
    269     * Return the number of hard links to the entry.
    270     * @return the number of hard links
    271     */
    272    public int getNlink() {
    273        return nlink;
    274    }
    275
    276    /**
    277     * Set the number of hard links.
    278     * @param nlink the number of hard links
    279     */
    280    public void setNlink(final int nlink) {
    281        this.nlink = nlink;
    282    }
    283
    284    /**
    285     * Get file creation time.
    286     * @return the creation time
    287     */
    288    public Date getCreationTime() {
    289        return new Date(ctime);
    290    }
    291
    292    /**
    293     * Set the file creation time.
    294     * @param ctime the creation time
    295     */
    296    public void setCreationTime(final Date ctime) {
    297        this.ctime = ctime.getTime();
    298    }
    299
    300    /**
    301     * Return the generation of the file.
    302     * @return the generation
    303     */
    304    public int getGeneration() {
    305        return generation;
    306    }
    307
    308    /**
    309     * Set the generation of the file.
    310     * @param generation the generation
    311     */
    312    public void setGeneration(final int generation) {
    313        this.generation = generation;
    314    }
    315
    316    /**
    317     * Has this file been deleted? (On valid on incremental dumps.)
    318     * @return whether the file has been deleted
    319     */
    320    public boolean isDeleted() {
    321        return isDeleted;
    322    }
    323
    324    /**
    325     * Set whether this file has been deleted.
    326     * @param isDeleted whether the file has been deleted
    327     */
    328    public void setDeleted(final boolean isDeleted) {
    329        this.isDeleted = isDeleted;
    330    }
    331
    332    /**
    333     * Return the offset within the archive
    334     * @return the offset
    335     */
    336    public long getOffset() {
    337        return offset;
    338    }
    339
    340    /**
    341     * Set the offset within the archive.
    342     * @param offset the offset
    343     */
    344    public void setOffset(final long offset) {
    345        this.offset = offset;
    346    }
    347
    348    /**
    349     * Return the tape volume where this file is located.
    350     * @return the volume
    351     */
    352    public int getVolume() {
    353        return volume;
    354    }
    355
    356    /**
    357     * Set the tape volume.
    358     * @param volume the volume
    359     */
    360    public void setVolume(final int volume) {
    361        this.volume = volume;
    362    }
    363
    364    /**
    365     * Return the type of the tape segment header.
    366     * @return the segment header
    367     */
    368    public DumpArchiveConstants.SEGMENT_TYPE getHeaderType() {
    369        return header.getType();
    370    }
    371
    372    /**
    373     * Return the number of records in this segment.
    374     * @return the number of records
    375     */
    376    public int getHeaderCount() {
    377        return header.getCount();
    378    }
    379
    380    /**
    381     * Return the number of sparse records in this segment.
    382     * @return the number of sparse records
    383     */
    384    public int getHeaderHoles() {
    385        return header.getHoles();
    386    }
    387
    388    /**
    389     * Is this a sparse record?
    390     * @param idx index of the record to check
    391     * @return whether this is a sparse record
    392     */
    393    public boolean isSparseRecord(final int idx) {
    394        return (header.getCdata(idx) & 0x01) == 0;
    395    }
    396
    397    @Override
    398    public int hashCode() {
    399        return ino;
    400    }
    401
    402    @Override
    403    public boolean equals(final Object o) {
    404        if (o == this) {
    405            return true;
    406        }
    407        if (o == null || !o.getClass().equals(getClass())) {
    408            return false;
    409        }
    410
    411        final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
    412
    413        if (rhs.header == null) {
    414            return false;
    415        }
    416
    417        if (ino != rhs.ino) {
    418            return false;
    419        }
    420
    421        // summary is always null right now, but this may change some day
    422        if ((summary == null && rhs.summary != null) // NOSONAR
    423                || (summary != null && !summary.equals(rhs.summary))) { // NOSONAR
    424            return false;
    425        }
    426
    427        return true;
    428    }
    429
    430    @Override
    431    public String toString() {
    432        return getName();
    433    }
    434
    435    /**
    436     * Populate the dump archive entry and tape segment header with
    437     * the contents of the buffer.
    438     *
    439     * @param buffer buffer to read content from
    440     */
    441    static DumpArchiveEntry parse(final byte[] buffer) {
    442        final DumpArchiveEntry entry = new DumpArchiveEntry();
    443        final TapeSegmentHeader header = entry.header;
    444
    445        header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(
    446                    buffer, 0));
    447
    448        //header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4));
    449        //header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32(
    450        //            buffer, 8));
    451        header.volume = DumpArchiveUtil.convert32(buffer, 12);
    452        //header.tapea = DumpArchiveUtil.convert32(buffer, 16);
    453        entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20);
    454
    455        //header.magic = DumpArchiveUtil.convert32(buffer, 24);
    456        //header.checksum = DumpArchiveUtil.convert32(buffer, 28);
    457        final int m = DumpArchiveUtil.convert16(buffer, 32);
    458
    459        // determine the type of the file.
    460        entry.setType(TYPE.find((m >> 12) & 0x0F));
    461
    462        // determine the standard permissions
    463        entry.setMode(m);
    464
    465        entry.nlink = DumpArchiveUtil.convert16(buffer, 34);
    466        // inumber, oldids?
    467        entry.setSize(DumpArchiveUtil.convert64(buffer, 40));
    468
    469        long t = (1000L * DumpArchiveUtil.convert32(buffer, 48)) +
    470            (DumpArchiveUtil.convert32(buffer, 52) / 1000);
    471        entry.setAccessTime(new Date(t));
    472        t = (1000L * DumpArchiveUtil.convert32(buffer, 56)) +
    473            (DumpArchiveUtil.convert32(buffer, 60) / 1000);
    474        entry.setLastModifiedDate(new Date(t));
    475        t = (1000L * DumpArchiveUtil.convert32(buffer, 64)) +
    476            (DumpArchiveUtil.convert32(buffer, 68) / 1000);
    477        entry.ctime = t;
    478
    479        // db: 72-119 - direct blocks
    480        // id: 120-131 - indirect blocks
    481        //entry.flags = DumpArchiveUtil.convert32(buffer, 132);
    482        //entry.blocks = DumpArchiveUtil.convert32(buffer, 136);
    483        entry.generation = DumpArchiveUtil.convert32(buffer, 140);
    484        entry.setUserId(DumpArchiveUtil.convert32(buffer, 144));
    485        entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148));
    486        // two 32-bit spare values.
    487        header.count = DumpArchiveUtil.convert32(buffer, 160);
    488
    489        header.holes = 0;
    490
    491        for (int i = 0; (i < 512) && (i < header.count); i++) {
    492            if (buffer[164 + i] == 0) {
    493                header.holes++;
    494            }
    495        }
    496
    497        System.arraycopy(buffer, 164, header.cdata, 0, 512);
    498
    499        entry.volume = header.getVolume();
    500
    501        //entry.isSummaryOnly = false;
    502        return entry;
    503    }
    504
    505    /**
    506     * Update entry with information from next tape segment header.
    507     */
    508    void update(final byte[] buffer) {
    509        header.volume = DumpArchiveUtil.convert32(buffer, 16);
    510        header.count = DumpArchiveUtil.convert32(buffer, 160);
    511
    512        header.holes = 0;
    513
    514        for (int i = 0; (i < 512) && (i < header.count); i++) {
    515            if (buffer[164 + i] == 0) {
    516                header.holes++;
    517            }
    518        }
    519
    520        System.arraycopy(buffer, 164, header.cdata, 0, 512);
    521    }
    522
    523    /**
    524     * Archive entry as stored on tape. There is one TSH for (at most)
    525     * every 512k in the file.
    526     */
    527    static class TapeSegmentHeader {
    528        private DumpArchiveConstants.SEGMENT_TYPE type;
    529        private int volume;
    530        private int ino;
    531        private int count;
    532        private int holes;
    533        private final byte[] cdata = new byte[512]; // map of any 'holes'
    534
    535        public DumpArchiveConstants.SEGMENT_TYPE getType() {
    536            return type;
    537        }
    538
    539        public int getVolume() {
    540            return volume;
    541        }
    542
    543        public int getIno() {
    544            return ino;
    545        }
    546
    547        void setIno(final int ino) {
    548            this.ino = ino;
    549        }
    550
    551        public int getCount() {
    552            return count;
    553        }
    554
    555        public int getHoles() {
    556            return holes;
    557        }
    558
    559        public int getCdata(final int idx) {
    560            return cdata[idx];
    561        }
    562    }
    563
    564    /**
    565     * Returns the name of the entry.
    566     *
    567     * <p>This method returns the raw name as it is stored inside of the archive.</p>
    568     *
    569     * @return the name of the entry.
    570     */
    571    @Override
    572    public String getName() {
    573        return name;
    574    }
    575
    576    /**
    577     * Returns the unmodified name of the entry.
    578     * @return the name of the entry.
    579     */
    580    String getOriginalName() {
    581        return originalName;
    582    }
    583
    584    /**
    585     * Sets the name of the entry.
    586     * @param name the name
    587     */
    588    public final void setName(String name) {
    589        this.originalName = name;
    590        if (name != null) {
    591            if (isDirectory() && !name.endsWith("/")) {
    592                name += "/";
    593            }
    594            if (name.startsWith("./")) {
    595                name = name.substring(2);
    596            }
    597        }
    598        this.name = name;
    599    }
    600
    601    /**
    602     * The last modified date.
    603     * @return the last modified date
    604     */
    605    @Override
    606    public Date getLastModifiedDate() {
    607        return new Date(mtime);
    608    }
    609
    610    /**
    611     * Is this a directory?
    612     * @return whether this is a directory
    613     */
    614    @Override
    615    public boolean isDirectory() {
    616        return type == TYPE.DIRECTORY;
    617    }
    618
    619    /**
    620     * Is this a regular file?
    621     * @return whether this is a regular file
    622     */
    623    public boolean isFile() {
    624        return type == TYPE.FILE;
    625    }
    626
    627    /**
    628     * Is this a network device?
    629     * @return whether this is a socket
    630     */
    631    public boolean isSocket() {
    632        return type == TYPE.SOCKET;
    633    }
    634
    635    /**
    636     * Is this a character device?
    637     * @return whether this is a character device
    638     */
    639    public boolean isChrDev() {
    640        return type == TYPE.CHRDEV;
    641    }
    642
    643    /**
    644     * Is this a block device?
    645     * @return whether this is a block device
    646     */
    647    public boolean isBlkDev() {
    648        return type == TYPE.BLKDEV;
    649    }
    650
    651    /**
    652     * Is this a fifo/pipe?
    653     * @return whether this is a fifo
    654     */
    655    public boolean isFifo() {
    656        return type == TYPE.FIFO;
    657    }
    658
    659    /**
    660     * Get the type of the entry.
    661     * @return the type
    662     */
    663    public TYPE getType() {
    664        return type;
    665    }
    666
    667    /**
    668     * Set the type of the entry.
    669     * @param type the type
    670     */
    671    public void setType(final TYPE type) {
    672        this.type = type;
    673    }
    674
    675    /**
    676     * Return the access permissions on the entry.
    677     * @return the access permissions
    678     */
    679    public int getMode() {
    680        return mode;
    681    }
    682
    683    /**
    684     * Set the access permissions on the entry.
    685     * @param mode the access permissions
    686     */
    687    public void setMode(final int mode) {
    688        this.mode = mode & 07777;
    689        this.permissions = PERMISSION.find(mode);
    690    }
    691
    692    /**
    693     * Returns the permissions on the entry.
    694     * @return the permissions
    695     */
    696    public Set<PERMISSION> getPermissions() {
    697        return permissions;
    698    }
    699
    700    /**
    701     * Returns the size of the entry.
    702     * @return the size
    703     */
    704    @Override
    705    public long getSize() {
    706        return isDirectory() ? SIZE_UNKNOWN : size;
    707    }
    708
    709    /**
    710     * Returns the size of the entry as read from the archive.
    711     */
    712    long getEntrySize() {
    713        return size;
    714    }
    715
    716    /**
    717     * Set the size of the entry.
    718     * @param size the size
    719     */
    720    public void setSize(final long size) {
    721        this.size = size;
    722    }
    723
    724    /**
    725     * Set the time the file was last modified.
    726     * @param mtime the last modified time
    727     */
    728    public void setLastModifiedDate(final Date mtime) {
    729        this.mtime = mtime.getTime();
    730    }
    731
    732    /**
    733     * Returns the time the file was last accessed.
    734     * @return the access time
    735     */
    736    public Date getAccessTime() {
    737        return new Date(atime);
    738    }
    739
    740    /**
    741     * Set the time the file was last accessed.
    742     * @param atime the access time
    743     */
    744    public void setAccessTime(final Date atime) {
    745        this.atime = atime.getTime();
    746    }
    747
    748    /**
    749     * Return the user id.
    750     * @return the user id
    751     */
    752    public int getUserId() {
    753        return uid;
    754    }
    755
    756    /**
    757     * Set the user id.
    758     * @param uid the user id
    759     */
    760    public void setUserId(final int uid) {
    761        this.uid = uid;
    762    }
    763
    764    /**
    765     * Return the group id
    766     * @return the group id
    767     */
    768    public int getGroupId() {
    769        return gid;
    770    }
    771
    772    /**
    773     * Set the group id.
    774     * @param gid the group id
    775     */
    776    public void setGroupId(final int gid) {
    777        this.gid = gid;
    778    }
    779
    780    public enum TYPE {
    781        WHITEOUT(14),
    782        SOCKET(12),
    783        LINK(10),
    784        FILE(8),
    785        BLKDEV(6),
    786        DIRECTORY(4),
    787        CHRDEV(2),
    788        FIFO(1),
    789        UNKNOWN(15);
    790
    791        private final int code;
    792
    793        TYPE(final int code) {
    794            this.code = code;
    795        }
    796
    797        public static TYPE find(final int code) {
    798            TYPE type = UNKNOWN;
    799
    800            for (final TYPE t : TYPE.values()) {
    801                if (code == t.code) {
    802                    type = t;
    803                }
    804            }
    805
    806            return type;
    807        }
    808    }
    809
    810    public enum PERMISSION {
    811        SETUID(04000),
    812        SETGUI(02000),
    813        STICKY(01000),
    814        USER_READ(00400),
    815        USER_WRITE(00200),
    816        USER_EXEC(00100),
    817        GROUP_READ(00040),
    818        GROUP_WRITE(00020),
    819        GROUP_EXEC(00010),
    820        WORLD_READ(00004),
    821        WORLD_WRITE(00002),
    822        WORLD_EXEC(00001);
    823
    824        private final int code;
    825
    826        PERMISSION(final int code) {
    827            this.code = code;
    828        }
    829
    830        public static Set<PERMISSION> find(final int code) {
    831            final Set<PERMISSION> set = new HashSet<>();
    832
    833            for (final PERMISSION p : PERMISSION.values()) {
    834                if ((code & p.code) == p.code) {
    835                    set.add(p);
    836                }
    837            }
    838
    839            if (set.isEmpty()) {
    840                return Collections.emptySet();
    841            }
    842
    843            return EnumSet.copyOf(set);
    844        }
    845    }
    846}