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 * https://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 "!<arch>" 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 }