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 {@code 0x60 0x0A} */
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         return Objects.equals(name, other.name);
151     }
152 
153     /**
154      * Gets the group ID.
155      *
156      * @return the group ID.
157      */
158     public int getGroupId() {
159         return groupId;
160     }
161 
162     /**
163      * Gets the last modified time in seconds since the epoch.
164      *
165      * @return the last modified date.
166      */
167     public long getLastModified() {
168         return lastModified;
169     }
170 
171     @Override
172     public Date getLastModifiedDate() {
173         return new Date(1000 * getLastModified());
174     }
175 
176     /**
177      * Gets the length.
178      *
179      * @return the length.
180      */
181     public long getLength() {
182         return length;
183     }
184 
185     /**
186      * Gets the mode.
187      *
188      * @return the mode.
189      */
190     public int getMode() {
191         return mode;
192     }
193 
194     @Override
195     public String getName() {
196         return name;
197     }
198 
199     @Override
200     public long getSize() {
201         return getLength();
202     }
203 
204     /**
205      * Gets the user ID.
206      *
207      * @return the user ID.
208      */
209     public int getUserId() {
210         return userId;
211     }
212 
213     @Override
214     public int hashCode() {
215         return Objects.hash(name);
216     }
217 
218     @Override
219     public boolean isDirectory() {
220         return false;
221     }
222 }