View Javadoc
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.ar;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.nio.file.Files;
24  import java.nio.file.LinkOption;
25  import java.nio.file.Path;
26  import java.util.Date;
27  import java.util.Objects;
28  
29  import org.apache.commons.compress.archivers.ArchiveEntry;
30  
31  /**
32   * Represents an archive entry in the "ar" format.
33   * <p>
34   * Each AR archive starts with "!&lt;arch&gt;" followed by a LF. After these 8 bytes the archive entries are listed. The format of an entry header is as it
35   * follows:
36   * </p>
37   *
38   * <pre>
39   * START BYTE   END BYTE    NAME                    FORMAT      LENGTH
40   * 0            15          File name               ASCII       16
41   * 16           27          Modification timestamp  Decimal     12
42   * 28           33          Owner ID                Decimal     6
43   * 34           39          Group ID                Decimal     6
44   * 40           47          File mode               Octal       8
45   * 48           57          File size (bytes)       Decimal     10
46   * 58           59          File magic              \140\012    2
47   * </pre>
48   * <p>
49   * This specifies that an ar archive entry header contains 60 bytes.
50   * </p>
51   * <p>
52   * Due to the limitation of the file name length to 16 bytes GNU and BSD has their own variants of this format. Currently Commons Compress can read but not
53   * write the GNU variant. It fully supports the BSD variant.
54   * </p>
55   *
56   * @see <a href="https://www.freebsd.org/cgi/man.cgi?query=ar&sektion=5">ar man page</a>
57   * @Immutable
58   */
59  public class ArArchiveEntry implements ArchiveEntry {
60  
61      /** The header for each entry */
62      public static final String HEADER = "!<arch>\n";
63  
64      /** The trailer for each entry */
65      public static final String TRAILER = "`\012";
66  
67      private static final int DEFAULT_MODE = 33188; // = (octal) 0100644
68  
69      /**
70       * SVR4/GNU adds a trailing / to names; BSD does not. They also vary in how names longer than 16 characters are represented. (Not yet fully supported by
71       * this implementation)
72       */
73      private final String name;
74      private final int userId;
75      private final int groupId;
76      private final int mode;
77      private final long lastModified;
78      private final long length;
79  
80      /**
81       * Creates a new instance using the attributes of the given file
82       *
83       * @param inputFile the file to create an entry from
84       * @param entryName the name of the entry
85       */
86      public ArArchiveEntry(final File inputFile, final String entryName) {
87          // TODO sort out mode
88          this(entryName, inputFile.isFile() ? inputFile.length() : 0, 0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
89      }
90  
91      /**
92       * Creates a new instance using the attributes of the given file
93       *
94       * @param inputPath the file to create an entry from
95       * @param entryName the name of the entry
96       * @param options   options indicating how symbolic links are handled.
97       * @throws IOException if an I/O error occurs.
98       * @since 1.21
99       */
100     public ArArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
101         this(entryName, Files.isRegularFile(inputPath, options) ? Files.size(inputPath) : 0, 0, 0, DEFAULT_MODE,
102                 Files.getLastModifiedTime(inputPath, options).toMillis() / 1000);
103     }
104 
105     /**
106      * Constructs a new instance using a couple of default values.
107      *
108      * <p>
109      * Sets userId and groupId to 0, the octal file mode to 644 and the last modified time to the current time.
110      * </p>
111      *
112      * @param name   name of the entry
113      * @param length length of the entry in bytes
114      */
115     public ArArchiveEntry(final String name, final long length) {
116         this(name, length, 0, 0, DEFAULT_MODE, System.currentTimeMillis() / 1000);
117     }
118 
119     /**
120      * Constructs a new instance.
121      *
122      * @param name         name of the entry
123      * @param length       length of the entry in bytes
124      * @param userId       numeric user id
125      * @param groupId      numeric group id
126      * @param mode         file mode
127      * @param lastModified last modified time in seconds since the epoch
128      */
129     public ArArchiveEntry(final String name, final long length, final int userId, final int groupId, final int mode, final long lastModified) {
130         this.name = name;
131         if (length < 0) {
132             throw new IllegalArgumentException("length must not be negative");
133         }
134         this.length = length;
135         this.userId = userId;
136         this.groupId = groupId;
137         this.mode = mode;
138         this.lastModified = lastModified;
139     }
140 
141     @Override
142     public boolean equals(final Object obj) {
143         if (this == obj) {
144             return true;
145         }
146         if (obj == null || getClass() != obj.getClass()) {
147             return false;
148         }
149         final ArArchiveEntry other = (ArArchiveEntry) obj;
150         if (name == null) {
151             return other.name == null;
152         }
153         return name.equals(other.name);
154     }
155 
156     /**
157      * Gets the group ID.
158      *
159      * @return the group ID.
160      */
161     public int getGroupId() {
162         return groupId;
163     }
164 
165     /**
166      * Gets the last modified time in seconds since the epoch.
167      *
168      * @return the last modified date.
169      */
170     public long getLastModified() {
171         return lastModified;
172     }
173 
174     @Override
175     public Date getLastModifiedDate() {
176         return new Date(1000 * getLastModified());
177     }
178 
179     /**
180      * Gets the length.
181      *
182      * @return the length.
183      */
184     public long getLength() {
185         return length;
186     }
187 
188     /**
189      * Gets the mode.
190      *
191      * @return the mode.
192      */
193     public int getMode() {
194         return mode;
195     }
196 
197     @Override
198     public String getName() {
199         return name;
200     }
201 
202     @Override
203     public long getSize() {
204         return this.getLength();
205     }
206 
207     /**
208      * Gets the user ID.
209      *
210      * @return the user ID.
211      */
212     public int getUserId() {
213         return userId;
214     }
215 
216     @Override
217     public int hashCode() {
218         return Objects.hash(name);
219     }
220 
221     @Override
222     public boolean isDirectory() {
223         return false;
224     }
225 }