• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
DumpArchiveEntry()214     public DumpArchiveEntry() {
215     }
216 
217     /**
218      * Constructor taking only filename.
219      * @param name pathname
220      * @param simpleName actual filename.
221      */
DumpArchiveEntry(final String name, final String simpleName)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      */
DumpArchiveEntry(final String name, final String simpleName, final int ino, final TYPE type)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      */
getSimpleName()248     public String getSimpleName() {
249         return simpleName;
250     }
251 
252     /**
253      * Sets the path of the entry.
254      * @param simpleName the simple name
255      */
setSimpleName(final String simpleName)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      */
getIno()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      */
getNlink()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      */
setNlink(final int nlink)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      */
getCreationTime()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      */
setCreationTime(final Date ctime)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      */
getGeneration()304     public int getGeneration() {
305         return generation;
306     }
307 
308     /**
309      * Set the generation of the file.
310      * @param generation the generation
311      */
setGeneration(final int generation)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      */
isDeleted()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      */
setDeleted(final boolean isDeleted)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      */
getOffset()336     public long getOffset() {
337         return offset;
338     }
339 
340     /**
341      * Set the offset within the archive.
342      * @param offset the offset
343      */
setOffset(final long offset)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      */
getVolume()352     public int getVolume() {
353         return volume;
354     }
355 
356     /**
357      * Set the tape volume.
358      * @param volume the volume
359      */
setVolume(final int volume)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      */
getHeaderType()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      */
getHeaderCount()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      */
getHeaderHoles()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      */
isSparseRecord(final int idx)393     public boolean isSparseRecord(final int idx) {
394         return (header.getCdata(idx) & 0x01) == 0;
395     }
396 
397     @Override
hashCode()398     public int hashCode() {
399         return ino;
400     }
401 
402     @Override
equals(final Object o)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
toString()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      */
parse(final byte[] buffer)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      */
update(final byte[] buffer)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 
getType()534         public DumpArchiveConstants.SEGMENT_TYPE getType() {
535             return type;
536         }
537 
getVolume()538         public int getVolume() {
539             return volume;
540         }
541 
getIno()542         public int getIno() {
543             return ino;
544         }
545 
setIno(final int ino)546         void setIno(final int ino) {
547             this.ino = ino;
548         }
549 
getCount()550         public int getCount() {
551             return count;
552         }
553 
getHoles()554         public int getHoles() {
555             return holes;
556         }
557 
getCdata(final int idx)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
getName()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      */
getOriginalName()579     String getOriginalName() {
580         return originalName;
581     }
582 
583     /**
584      * Sets the name of the entry.
585      * @param name the name
586      */
setName(String name)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
getLastModifiedDate()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
isDirectory()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      */
isFile()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      */
isSocket()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      */
isChrDev()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      */
isBlkDev()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      */
isFifo()654     public boolean isFifo() {
655         return type == TYPE.FIFO;
656     }
657 
658     /**
659      * Get the type of the entry.
660      * @return the type
661      */
getType()662     public TYPE getType() {
663         return type;
664     }
665 
666     /**
667      * Set the type of the entry.
668      * @param type the type
669      */
setType(final TYPE type)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      */
getMode()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      */
setMode(final int mode)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      */
getPermissions()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
getSize()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      */
getEntrySize()711     long getEntrySize() {
712         return size;
713     }
714 
715     /**
716      * Set the size of the entry.
717      * @param size the size
718      */
setSize(final long size)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      */
setLastModifiedDate(final Date mtime)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      */
getAccessTime()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      */
setAccessTime(final Date atime)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      */
getUserId()751     public int getUserId() {
752         return uid;
753     }
754 
755     /**
756      * Set the user id.
757      * @param uid the user id
758      */
setUserId(final int uid)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      */
getGroupId()767     public int getGroupId() {
768         return gid;
769     }
770 
771     /**
772      * Set the group id.
773      * @param gid the group id
774      */
setGroupId(final int gid)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 
TYPE(final int code)792         TYPE(final int code) {
793             this.code = code;
794         }
795 
find(final int code)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 
PERMISSION(final int code)825         PERMISSION(final int code) {
826             this.code = code;
827         }
828 
find(final int code)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