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 "!<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 }