Home | History | Annotate | Download | only in dump
      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 import org.apache.commons.compress.archivers.ArchiveEntry;
     27 
     28 /**
     29  * This class represents an entry in a Dump archive. It consists
     30  * 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
     33  * an archive are instantiated with the DumpArchiveEntry( byte[] )
     34  * constructor. These entries will be used when extracting from
     35  * or listing the contents of an archive. These entries have their
     36  * header filled in using the header bytes. They also set the File
     37  * to null, since they reference an archive entry not a file.
     38  * <p>
     39  * DumpEntries can also be constructed from nothing but a name.
     40  * This allows the programmer to construct the entry by hand, for
     41  * instance when only an InputStream is available for writing to
     42  * the archive, and the header information is constructed from
     43  * other information. In this case the header fields are set to
     44  * defaults and the File is set to null.
     45  *
     46  * <p>
     47  * The C structure for a Dump Entry's header is:
     48  * <pre>
     49  * #define TP_BSIZE    1024          // size of each file block
     50  * #define NTREC       10            // number of blocks to write at once
     51  * #define HIGHDENSITYTREC 32        // number of blocks to write on high-density tapes
     52  * #define TP_NINDIR   (TP_BSIZE/2)  // number if indirect inodes in record
     53  * #define TP_NINOS    (TP_NINDIR / sizeof (int32_t))
     54  * #define LBLSIZE     16
     55  * #define NAMELEN     64
     56  *
     57  * #define OFS_MAGIC     (int)60011  // old format magic value
     58  * #define NFS_MAGIC     (int)60012  // new format magic value
     59  * #define FS_UFS2_MAGIC (int)0x19540119
     60  * #define CHECKSUM      (int)84446  // constant used in checksum algorithm
     61  *
     62  * struct  s_spcl {
     63  *   int32_t c_type;             // record type (see below)
     64  *   int32_t <b>c_date</b>;             // date of this dump
     65  *   int32_t <b>c_ddate</b>;            // date of previous dump
     66  *   int32_t c_volume;           // dump volume number
     67  *   u_int32_t c_tapea;          // logical block of this record
     68  *   dump_ino_t c_ino;           // number of inode
     69  *   int32_t <b>c_magic</b>;            // magic number (see above)
     70  *   int32_t c_checksum;         // record checksum
     71  * #ifdef  __linux__
     72  *   struct  new_bsd_inode c_dinode;
     73  * #else
     74  * #ifdef sunos
     75  *   struct  new_bsd_inode c_dinode;
     76  * #else
     77  *   struct  dinode  c_dinode;   // ownership and mode of inode
     78  * #endif
     79  * #endif
     80  *   int32_t c_count;            // number of valid c_addr entries
     81  *   union u_data c_data;        // see above
     82  *   char    <b>c_label[LBLSIZE]</b>;   // dump label
     83  *   int32_t <b>c_level</b>;            // level of this dump
     84  *   char    <b>c_filesys[NAMELEN]</b>; // name of dumpped file system
     85  *   char    <b>c_dev[NAMELEN]</b>;     // name of dumpped device
     86  *   char    <b>c_host[NAMELEN]</b>;    // name of dumpped host
     87  *   int32_t c_flags;            // additional information (see below)
     88  *   int32_t c_firstrec;         // first record on volume
     89  *   int32_t c_ntrec;            // blocksize on volume
     90  *   int32_t c_extattributes;    // additional inode info (see below)
     91  *   int32_t c_spare[30];        // reserved for future uses
     92  * } s_spcl;
     93  *
     94  * //
     95  * // flag values
     96  * //
     97  * #define DR_NEWHEADER     0x0001  // new format tape header
     98  * #define DR_NEWINODEFMT   0x0002  // new format inodes on tape
     99  * #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 filenames
    161  * for a single physical file. You must read the contents of the directory
    162  * entries to learn the mapping(s) from filename 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  */
    181 public 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 filename.
    219      * @param name pathname
    220      * @param simpleName actual filename.
    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         } else if (o == null || !o.getClass().equals(getClass())) {
    407             return false;
    408         }
    409 
    410         final DumpArchiveEntry rhs = (DumpArchiveEntry) o;
    411 
    412         if (rhs.header == null) {
    413             return false;
    414         }
    415 
    416         if (ino != rhs.ino) {
    417             return false;
    418         }
    419 
    420         // summary is always null right now, but this may change some day
    421         if ((summary == null && rhs.summary != null) // NOSONAR
    422                 || (summary != null && !summary.equals(rhs.summary))) { // NOSONAR
    423             return false;
    424         }
    425 
    426         return true;
    427     }
    428 
    429     @Override
    430     public String toString() {
    431         return getName();
    432     }
    433 
    434     /**
    435      * Populate the dump archive entry and tape segment header with
    436      * the contents of the buffer.
    437      *
    438      * @param buffer buffer to read content from
    439      */
    440     static DumpArchiveEntry parse(final byte[] buffer) {
    441         final DumpArchiveEntry entry = new DumpArchiveEntry();
    442         final TapeSegmentHeader header = entry.header;
    443 
    444         header.type = DumpArchiveConstants.SEGMENT_TYPE.find(DumpArchiveUtil.convert32(
    445                     buffer, 0));
    446 
    447         //header.dumpDate = new Date(1000L * DumpArchiveUtil.convert32(buffer, 4));
    448         //header.previousDumpDate = new Date(1000L * DumpArchiveUtil.convert32(
    449         //            buffer, 8));
    450         header.volume = DumpArchiveUtil.convert32(buffer, 12);
    451         //header.tapea = DumpArchiveUtil.convert32(buffer, 16);
    452         entry.ino = header.ino = DumpArchiveUtil.convert32(buffer, 20);
    453 
    454         //header.magic = DumpArchiveUtil.convert32(buffer, 24);
    455         //header.checksum = DumpArchiveUtil.convert32(buffer, 28);
    456         final int m = DumpArchiveUtil.convert16(buffer, 32);
    457 
    458         // determine the type of the file.
    459         entry.setType(TYPE.find((m >> 12) & 0x0F));
    460 
    461         // determine the standard permissions
    462         entry.setMode(m);
    463 
    464         entry.nlink = DumpArchiveUtil.convert16(buffer, 34);
    465         // inumber, oldids?
    466         entry.setSize(DumpArchiveUtil.convert64(buffer, 40));
    467 
    468         long t = (1000L * DumpArchiveUtil.convert32(buffer, 48)) +
    469             (DumpArchiveUtil.convert32(buffer, 52) / 1000);
    470         entry.setAccessTime(new Date(t));
    471         t = (1000L * DumpArchiveUtil.convert32(buffer, 56)) +
    472             (DumpArchiveUtil.convert32(buffer, 60) / 1000);
    473         entry.setLastModifiedDate(new Date(t));
    474         t = (1000L * DumpArchiveUtil.convert32(buffer, 64)) +
    475             (DumpArchiveUtil.convert32(buffer, 68) / 1000);
    476         entry.ctime = t;
    477 
    478         // db: 72-119 - direct blocks
    479         // id: 120-131 - indirect blocks
    480         //entry.flags = DumpArchiveUtil.convert32(buffer, 132);
    481         //entry.blocks = DumpArchiveUtil.convert32(buffer, 136);
    482         entry.generation = DumpArchiveUtil.convert32(buffer, 140);
    483         entry.setUserId(DumpArchiveUtil.convert32(buffer, 144));
    484         entry.setGroupId(DumpArchiveUtil.convert32(buffer, 148));
    485         // two 32-bit spare values.
    486         header.count = DumpArchiveUtil.convert32(buffer, 160);
    487 
    488         header.holes = 0;
    489 
    490         for (int i = 0; (i < 512) && (i < header.count); i++) {
    491             if (buffer[164 + i] == 0) {
    492                 header.holes++;
    493             }
    494         }
    495 
    496         System.arraycopy(buffer, 164, header.cdata, 0, 512);
    497 
    498         entry.volume = header.getVolume();
    499 
    500         //entry.isSummaryOnly = false;
    501         return entry;
    502     }
    503 
    504     /**
    505      * Update entry with information from next tape segment header.
    506      */
    507     void update(final byte[] buffer) {
    508         header.volume = DumpArchiveUtil.convert32(buffer, 16);
    509         header.count = DumpArchiveUtil.convert32(buffer, 160);
    510 
    511         header.holes = 0;
    512 
    513         for (int i = 0; (i < 512) && (i < header.count); i++) {
    514             if (buffer[164 + i] == 0) {
    515                 header.holes++;
    516             }
    517         }
    518 
    519         System.arraycopy(buffer, 164, header.cdata, 0, 512);
    520     }
    521 
    522     /**
    523      * Archive entry as stored on tape. There is one TSH for (at most)
    524      * every 512k in the file.
    525      */
    526     static class TapeSegmentHeader {
    527         private DumpArchiveConstants.SEGMENT_TYPE type;
    528         private int volume;
    529         private int ino;
    530         private int count;
    531         private int holes;
    532         private final byte[] cdata = new byte[512]; // map of any 'holes'
    533 
    534         public DumpArchiveConstants.SEGMENT_TYPE getType() {
    535             return type;
    536         }
    537 
    538         public int getVolume() {
    539             return volume;
    540         }
    541 
    542         public int getIno() {
    543             return ino;
    544         }
    545 
    546         void setIno(final int ino) {
    547             this.ino = ino;
    548         }
    549 
    550         public int getCount() {
    551             return count;
    552         }
    553 
    554         public int getHoles() {
    555             return holes;
    556         }
    557 
    558         public int getCdata(final int idx) {
    559             return cdata[idx];
    560         }
    561     }
    562 
    563     /**
    564      * Returns the name of the entry.
    565      *
    566      * <p>This method returns the raw name as it is stored inside of the archive.</p>
    567      *
    568      * @return the name of the entry.
    569      */
    570     @Override
    571     public String getName() {
    572         return name;
    573     }
    574 
    575     /**
    576      * Returns the unmodified name of the entry.
    577      * @return the name of the entry.
    578      */
    579     String getOriginalName() {
    580         return originalName;
    581     }
    582 
    583     /**
    584      * Sets the name of the entry.
    585      * @param name the name
    586      */
    587     public final void setName(String name) {
    588         this.originalName = name;
    589         if (name != null) {
    590             if (isDirectory() && !name.endsWith("/")) {
    591                 name += "/";
    592             }
    593             if (name.startsWith("./")) {
    594                 name = name.substring(2);
    595             }
    596         }
    597         this.name = name;
    598     }
    599 
    600     /**
    601      * The last modified date.
    602      * @return the last modified date
    603      */
    604     @Override
    605     public Date getLastModifiedDate() {
    606         return new Date(mtime);
    607     }
    608 
    609     /**
    610      * Is this a directory?
    611      * @return whether this is a directory
    612      */
    613     @Override
    614     public boolean isDirectory() {
    615         return type == TYPE.DIRECTORY;
    616     }
    617 
    618     /**
    619      * Is this a regular file?
    620      * @return whether this is a regular file
    621      */
    622     public boolean isFile() {
    623         return type == TYPE.FILE;
    624     }
    625 
    626     /**
    627      * Is this a network device?
    628      * @return whether this is a socket
    629      */
    630     public boolean isSocket() {
    631         return type == TYPE.SOCKET;
    632     }
    633 
    634     /**
    635      * Is this a character device?
    636      * @return whether this is a character device
    637      */
    638     public boolean isChrDev() {
    639         return type == TYPE.CHRDEV;
    640     }
    641 
    642     /**
    643      * Is this a block device?
    644      * @return whether this is a block device
    645      */
    646     public boolean isBlkDev() {
    647         return type == TYPE.BLKDEV;
    648     }
    649 
    650     /**
    651      * Is this a fifo/pipe?
    652      * @return whether this is a fifo
    653      */
    654     public boolean isFifo() {
    655         return type == TYPE.FIFO;
    656     }
    657 
    658     /**
    659      * Get the type of the entry.
    660      * @return the type
    661      */
    662     public TYPE getType() {
    663         return type;
    664     }
    665 
    666     /**
    667      * Set the type of the entry.
    668      * @param type the type
    669      */
    670     public void setType(final TYPE type) {
    671         this.type = type;
    672     }
    673 
    674     /**
    675      * Return the access permissions on the entry.
    676      * @return the access permissions
    677      */
    678     public int getMode() {
    679         return mode;
    680     }
    681 
    682     /**
    683      * Set the access permissions on the entry.
    684      * @param mode the access permissions
    685      */
    686     public void setMode(final int mode) {
    687         this.mode = mode & 07777;
    688         this.permissions = PERMISSION.find(mode);
    689     }
    690 
    691     /**
    692      * Returns the permissions on the entry.
    693      * @return the permissions
    694      */
    695     public Set<PERMISSION> getPermissions() {
    696         return permissions;
    697     }
    698 
    699     /**
    700      * Returns the size of the entry.
    701      * @return the size
    702      */
    703     @Override
    704     public long getSize() {
    705         return isDirectory() ? SIZE_UNKNOWN : size;
    706     }
    707 
    708     /**
    709      * Returns the size of the entry as read from the archive.
    710      */
    711     long getEntrySize() {
    712         return size;
    713     }
    714 
    715     /**
    716      * Set the size of the entry.
    717      * @param size the size
    718      */
    719     public void setSize(final long size) {
    720         this.size = size;
    721     }
    722 
    723     /**
    724      * Set the time the file was last modified.
    725      * @param mtime the last modified time
    726      */
    727     public void setLastModifiedDate(final Date mtime) {
    728         this.mtime = mtime.getTime();
    729     }
    730 
    731     /**
    732      * Returns the time the file was last accessed.
    733      * @return the access time
    734      */
    735     public Date getAccessTime() {
    736         return new Date(atime);
    737     }
    738 
    739     /**
    740      * Set the time the file was last accessed.
    741      * @param atime the access time
    742      */
    743     public void setAccessTime(final Date atime) {
    744         this.atime = atime.getTime();
    745     }
    746 
    747     /**
    748      * Return the user id.
    749      * @return the user id
    750      */
    751     public int getUserId() {
    752         return uid;
    753     }
    754 
    755     /**
    756      * Set the user id.
    757      * @param uid the user id
    758      */
    759     public void setUserId(final int uid) {
    760         this.uid = uid;
    761     }
    762 
    763     /**
    764      * Return the group id
    765      * @return the group id
    766      */
    767     public int getGroupId() {
    768         return gid;
    769     }
    770 
    771     /**
    772      * Set the group id.
    773      * @param gid the group id
    774      */
    775     public void setGroupId(final int gid) {
    776         this.gid = gid;
    777     }
    778 
    779     public enum TYPE {
    780         WHITEOUT(14),
    781         SOCKET(12),
    782         LINK(10),
    783         FILE(8),
    784         BLKDEV(6),
    785         DIRECTORY(4),
    786         CHRDEV(2),
    787         FIFO(1),
    788         UNKNOWN(15);
    789 
    790         private int code;
    791 
    792         TYPE(final int code) {
    793             this.code = code;
    794         }
    795 
    796         public static TYPE find(final int code) {
    797             TYPE type = UNKNOWN;
    798 
    799             for (final TYPE t : TYPE.values()) {
    800                 if (code == t.code) {
    801                     type = t;
    802                 }
    803             }
    804 
    805             return type;
    806         }
    807     }
    808 
    809     public enum PERMISSION {
    810         SETUID(04000),
    811         SETGUI(02000),
    812         STICKY(01000),
    813         USER_READ(00400),
    814         USER_WRITE(00200),
    815         USER_EXEC(00100),
    816         GROUP_READ(00040),
    817         GROUP_WRITE(00020),
    818         GROUP_EXEC(00010),
    819         WORLD_READ(00004),
    820         WORLD_WRITE(00002),
    821         WORLD_EXEC(00001);
    822 
    823         private int code;
    824 
    825         PERMISSION(final int code) {
    826             this.code = code;
    827         }
    828 
    829         public static Set<PERMISSION> find(final int code) {
    830             final Set<PERMISSION> set = new HashSet<>();
    831 
    832             for (final PERMISSION p : PERMISSION.values()) {
    833                 if ((code & p.code) == p.code) {
    834                     set.add(p);
    835                 }
    836             }
    837 
    838             if (set.isEmpty()) {
    839                 return Collections.emptySet();
    840             }
    841 
    842             return EnumSet.copyOf(set);
    843         }
    844     }
    845 }
    846