You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

TarOutputStream.java 24 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. /*
  19. * This package is based on the work done by Timothy Gerard Endres
  20. * (time@ice.com) to whom the Ant project is very grateful for his great code.
  21. */
  22. package org.apache.tools.tar;
  23. import java.io.FilterOutputStream;
  24. import java.io.IOException;
  25. import java.io.OutputStream;
  26. import java.io.StringWriter;
  27. import java.nio.ByteBuffer;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.Map;
  31. import org.apache.tools.zip.ZipEncoding;
  32. import org.apache.tools.zip.ZipEncodingHelper;
  33. /**
  34. * The TarOutputStream writes a UNIX tar archive as an OutputStream.
  35. * Methods are provided to put entries, and then write their contents
  36. * by writing to this stream using write().
  37. *
  38. */
  39. public class TarOutputStream extends FilterOutputStream {
  40. /** Fail if a long file name is required in the archive. */
  41. public static final int LONGFILE_ERROR = 0;
  42. /** Long paths will be truncated in the archive. */
  43. public static final int LONGFILE_TRUNCATE = 1;
  44. /** GNU tar extensions are used to store long file names in the archive. */
  45. public static final int LONGFILE_GNU = 2;
  46. /** POSIX/PAX extensions are used to store long file names in the archive. */
  47. public static final int LONGFILE_POSIX = 3;
  48. /** Fail if a big number (e.g. size > 8GiB) is required in the archive. */
  49. public static final int BIGNUMBER_ERROR = 0;
  50. /** star/GNU tar/BSD tar extensions are used to store big number in the archive. */
  51. public static final int BIGNUMBER_STAR = 1;
  52. /** POSIX/PAX extensions are used to store big numbers in the archive. */
  53. public static final int BIGNUMBER_POSIX = 2;
  54. // CheckStyle:VisibilityModifier OFF - bc
  55. protected boolean debug;
  56. protected long currSize;
  57. protected String currName;
  58. protected long currBytes;
  59. protected byte[] oneBuf;
  60. protected byte[] recordBuf;
  61. protected int assemLen;
  62. protected byte[] assemBuf;
  63. protected TarBuffer buffer;
  64. protected int longFileMode = LONGFILE_ERROR;
  65. // CheckStyle:VisibilityModifier ON
  66. private int bigNumberMode = BIGNUMBER_ERROR;
  67. private boolean closed = false;
  68. /** Indicates if putNextEntry has been called without closeEntry */
  69. private boolean haveUnclosedEntry = false;
  70. /** indicates if this archive is finished */
  71. private boolean finished = false;
  72. private final ZipEncoding encoding;
  73. private boolean addPaxHeadersForNonAsciiNames = false;
  74. private static final ZipEncoding ASCII =
  75. ZipEncodingHelper.getZipEncoding("ASCII");
  76. /**
  77. * Constructor for TarInputStream.
  78. * @param os the output stream to use
  79. */
  80. public TarOutputStream(OutputStream os) {
  81. this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
  82. }
  83. /**
  84. * Constructor for TarInputStream.
  85. * @param os the output stream to use
  86. * @param encoding name of the encoding to use for file names
  87. */
  88. public TarOutputStream(OutputStream os, String encoding) {
  89. this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
  90. }
  91. /**
  92. * Constructor for TarInputStream.
  93. * @param os the output stream to use
  94. * @param blockSize the block size to use
  95. */
  96. public TarOutputStream(OutputStream os, int blockSize) {
  97. this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  98. }
  99. /**
  100. * Constructor for TarInputStream.
  101. * @param os the output stream to use
  102. * @param blockSize the block size to use
  103. * @param encoding name of the encoding to use for file names
  104. */
  105. public TarOutputStream(OutputStream os, int blockSize, String encoding) {
  106. this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
  107. }
  108. /**
  109. * Constructor for TarInputStream.
  110. * @param os the output stream to use
  111. * @param blockSize the block size to use
  112. * @param recordSize the record size to use
  113. */
  114. public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
  115. this(os, blockSize, recordSize, null);
  116. }
  117. /**
  118. * Constructor for TarInputStream.
  119. * @param os the output stream to use
  120. * @param blockSize the block size to use
  121. * @param recordSize the record size to use
  122. * @param encoding name of the encoding to use for file names
  123. */
  124. public TarOutputStream(OutputStream os, int blockSize, int recordSize,
  125. String encoding) {
  126. super(os);
  127. this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
  128. this.buffer = new TarBuffer(os, blockSize, recordSize);
  129. this.debug = false;
  130. this.assemLen = 0;
  131. this.assemBuf = new byte[recordSize];
  132. this.recordBuf = new byte[recordSize];
  133. this.oneBuf = new byte[1];
  134. }
  135. /**
  136. * Set the long file mode.
  137. * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
  138. * This specifies the treatment of long file names (names >= TarConstants.NAMELEN).
  139. * Default is LONGFILE_ERROR.
  140. * @param longFileMode the mode to use
  141. */
  142. public void setLongFileMode(int longFileMode) {
  143. this.longFileMode = longFileMode;
  144. }
  145. /**
  146. * Set the big number mode.
  147. * This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or BIGNUMBER_STAR(2).
  148. * This specifies the treatment of big files (sizes > TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header.
  149. * Default is BIGNUMBER_ERROR.
  150. * @param bigNumberMode the mode to use
  151. */
  152. public void setBigNumberMode(int bigNumberMode) {
  153. this.bigNumberMode = bigNumberMode;
  154. }
  155. /**
  156. * Whether to add a PAX extension header for non-ASCII file names.
  157. */
  158. public void setAddPaxHeadersForNonAsciiNames(boolean b) {
  159. addPaxHeadersForNonAsciiNames = b;
  160. }
  161. /**
  162. * Sets the debugging flag.
  163. *
  164. * @param debugF True to turn on debugging.
  165. */
  166. public void setDebug(boolean debugF) {
  167. this.debug = debugF;
  168. }
  169. /**
  170. * Sets the debugging flag in this stream's TarBuffer.
  171. *
  172. * @param debug True to turn on debugging.
  173. */
  174. public void setBufferDebug(boolean debug) {
  175. buffer.setDebug(debug);
  176. }
  177. /**
  178. * Ends the TAR archive without closing the underlying OutputStream.
  179. *
  180. * An archive consists of a series of file entries terminated by an
  181. * end-of-archive entry, which consists of two 512 blocks of zero bytes.
  182. * POSIX.1 requires two EOF records, like some other implementations.
  183. *
  184. * @throws IOException on error
  185. */
  186. public void finish() throws IOException {
  187. if (finished) {
  188. throw new IOException("This archive has already been finished");
  189. }
  190. if (haveUnclosedEntry) {
  191. throw new IOException("This archives contains unclosed entries.");
  192. }
  193. writeEOFRecord();
  194. writeEOFRecord();
  195. buffer.flushBlock();
  196. finished = true;
  197. }
  198. /**
  199. * Ends the TAR archive and closes the underlying OutputStream.
  200. * This means that finish() is called followed by calling the
  201. * TarBuffer's close().
  202. * @throws IOException on error
  203. */
  204. @Override
  205. public void close() throws IOException {
  206. if(!finished) {
  207. finish();
  208. }
  209. if (!closed) {
  210. buffer.close();
  211. out.close();
  212. closed = true;
  213. }
  214. }
  215. /**
  216. * Get the record size being used by this stream's TarBuffer.
  217. *
  218. * @return The TarBuffer record size.
  219. */
  220. public int getRecordSize() {
  221. return buffer.getRecordSize();
  222. }
  223. /**
  224. * Put an entry on the output stream. This writes the entry's
  225. * header record and positions the output stream for writing
  226. * the contents of the entry. Once this method is called, the
  227. * stream is ready for calls to write() to write the entry's
  228. * contents. Once the contents are written, closeEntry()
  229. * <B>MUST</B> be called to ensure that all buffered data
  230. * is completely written to the output stream.
  231. *
  232. * @param entry The TarEntry to be written to the archive.
  233. * @throws IOException on error
  234. */
  235. public void putNextEntry(TarEntry entry) throws IOException {
  236. if(finished) {
  237. throw new IOException("Stream has already been finished");
  238. }
  239. Map<String, String> paxHeaders = new HashMap<String, String>();
  240. final String entryName = entry.getName();
  241. boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path",
  242. TarConstants.LF_GNUTYPE_LONGNAME, "file name");
  243. final String linkName = entry.getLinkName();
  244. boolean paxHeaderContainsLinkPath = linkName != null && linkName.length() > 0
  245. && handleLongName(entry, linkName, paxHeaders, "linkpath",
  246. TarConstants.LF_GNUTYPE_LONGLINK, "link name");
  247. if (bigNumberMode == BIGNUMBER_POSIX) {
  248. addPaxHeadersForBigNumbers(paxHeaders, entry);
  249. } else if (bigNumberMode != BIGNUMBER_STAR) {
  250. failForBigNumbers(entry);
  251. }
  252. if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
  253. && !ASCII.canEncode(entryName)) {
  254. paxHeaders.put("path", entryName);
  255. }
  256. if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsLinkPath
  257. && (entry.isLink() || entry.isSymbolicLink())
  258. && !ASCII.canEncode(linkName)) {
  259. paxHeaders.put("linkpath", linkName);
  260. }
  261. if (paxHeaders.size() > 0) {
  262. writePaxHeaders(entry, entryName, paxHeaders);
  263. }
  264. entry.writeEntryHeader(recordBuf, encoding,
  265. bigNumberMode == BIGNUMBER_STAR);
  266. buffer.writeRecord(recordBuf);
  267. currBytes = 0;
  268. if (entry.isDirectory()) {
  269. currSize = 0;
  270. } else {
  271. currSize = entry.getSize();
  272. }
  273. currName = entryName;
  274. haveUnclosedEntry = true;
  275. }
  276. /**
  277. * Close an entry. This method MUST be called for all file
  278. * entries that contain data. The reason is that we must
  279. * buffer data written to the stream in order to satisfy
  280. * the buffer's record based writes. Thus, there may be
  281. * data fragments still being assembled that must be written
  282. * to the output stream before this entry is closed and the
  283. * next entry written.
  284. * @throws IOException on error
  285. */
  286. public void closeEntry() throws IOException {
  287. if (finished) {
  288. throw new IOException("Stream has already been finished");
  289. }
  290. if (!haveUnclosedEntry){
  291. throw new IOException("No current entry to close");
  292. }
  293. if (assemLen > 0) {
  294. for (int i = assemLen; i < assemBuf.length; ++i) {
  295. assemBuf[i] = 0;
  296. }
  297. buffer.writeRecord(assemBuf);
  298. currBytes += assemLen;
  299. assemLen = 0;
  300. }
  301. if (currBytes < currSize) {
  302. throw new IOException("entry '" + currName + "' closed at '"
  303. + currBytes
  304. + "' before the '" + currSize
  305. + "' bytes specified in the header were written");
  306. }
  307. haveUnclosedEntry = false;
  308. }
  309. /**
  310. * Writes a byte to the current tar archive entry.
  311. *
  312. * This method simply calls read( byte[], int, int ).
  313. *
  314. * @param b The byte written.
  315. * @throws IOException on error
  316. */
  317. @Override
  318. public void write(int b) throws IOException {
  319. oneBuf[0] = (byte) b;
  320. write(oneBuf, 0, 1);
  321. }
  322. /**
  323. * Writes bytes to the current tar archive entry.
  324. *
  325. * This method simply calls write( byte[], int, int ).
  326. *
  327. * @param wBuf The buffer to write to the archive.
  328. * @throws IOException on error
  329. */
  330. @Override
  331. public void write(byte[] wBuf) throws IOException {
  332. write(wBuf, 0, wBuf.length);
  333. }
  334. /**
  335. * Writes bytes to the current tar archive entry. This method
  336. * is aware of the current entry and will throw an exception if
  337. * you attempt to write bytes past the length specified for the
  338. * current entry. The method is also (painfully) aware of the
  339. * record buffering required by TarBuffer, and manages buffers
  340. * that are not a multiple of recordsize in length, including
  341. * assembling records from small buffers.
  342. *
  343. * @param wBuf The buffer to write to the archive.
  344. * @param wOffset The offset in the buffer from which to get bytes.
  345. * @param numToWrite The number of bytes to write.
  346. * @throws IOException on error
  347. */
  348. @Override
  349. public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
  350. if ((currBytes + numToWrite) > currSize) {
  351. throw new IOException("request to write '" + numToWrite
  352. + "' bytes exceeds size in header of '"
  353. + currSize + "' bytes for entry '"
  354. + currName + "'");
  355. //
  356. // We have to deal with assembly!!!
  357. // The programmer can be writing little 32 byte chunks for all
  358. // we know, and we must assemble complete records for writing.
  359. // REVIEW Maybe this should be in TarBuffer? Could that help to
  360. // eliminate some of the buffer copying.
  361. //
  362. }
  363. if (assemLen > 0) {
  364. if ((assemLen + numToWrite) >= recordBuf.length) {
  365. int aLen = recordBuf.length - assemLen;
  366. System.arraycopy(assemBuf, 0, recordBuf, 0,
  367. assemLen);
  368. System.arraycopy(wBuf, wOffset, recordBuf,
  369. assemLen, aLen);
  370. buffer.writeRecord(recordBuf);
  371. currBytes += recordBuf.length;
  372. wOffset += aLen;
  373. numToWrite -= aLen;
  374. assemLen = 0;
  375. } else {
  376. System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
  377. numToWrite);
  378. wOffset += numToWrite;
  379. assemLen += numToWrite;
  380. numToWrite = 0;
  381. }
  382. }
  383. //
  384. // When we get here we have EITHER:
  385. // o An empty "assemble" buffer.
  386. // o No bytes to write (numToWrite == 0)
  387. //
  388. while (numToWrite > 0) {
  389. if (numToWrite < recordBuf.length) {
  390. System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
  391. numToWrite);
  392. assemLen += numToWrite;
  393. break;
  394. }
  395. buffer.writeRecord(wBuf, wOffset);
  396. int num = recordBuf.length;
  397. currBytes += num;
  398. numToWrite -= num;
  399. wOffset += num;
  400. }
  401. }
  402. /**
  403. * Writes a PAX extended header with the given map as contents.
  404. */
  405. void writePaxHeaders(TarEntry entry,
  406. String entryName,
  407. Map<String, String> headers) throws IOException {
  408. String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
  409. if (name.length() >= TarConstants.NAMELEN) {
  410. name = name.substring(0, TarConstants.NAMELEN - 1);
  411. }
  412. while (name.endsWith("/")) {
  413. // TarEntry's constructor would think this is a directory
  414. // and not allow any data to be written
  415. name = name.substring(0, name.length() - 1);
  416. }
  417. TarEntry pex = new TarEntry(name,
  418. TarConstants.LF_PAX_EXTENDED_HEADER_LC);
  419. transferModTime(entry, pex);
  420. StringWriter w = new StringWriter();
  421. for (Map.Entry<String, String> h : headers.entrySet()) {
  422. String key = h.getKey();
  423. String value = h.getValue();
  424. int len = key.length() + value.length()
  425. + 3 /* blank, equals and newline */
  426. + 2 /* guess 9 < actual length < 100 */;
  427. String line = len + " " + key + "=" + value + "\n";
  428. int actualLength = line.getBytes("UTF-8").length;
  429. while (len != actualLength) {
  430. // Adjust for cases where length < 10 or > 100
  431. // or where UTF-8 encoding isn't a single octet
  432. // per character.
  433. // Must be in loop as size may go from 99 to 100 in
  434. // first pass so we'd need a second.
  435. len = actualLength;
  436. line = len + " " + key + "=" + value + "\n";
  437. actualLength = line.getBytes("UTF-8").length;
  438. }
  439. w.write(line);
  440. }
  441. byte[] data = w.toString().getBytes("UTF-8");
  442. pex.setSize(data.length);
  443. putNextEntry(pex);
  444. write(data);
  445. closeEntry();
  446. }
  447. private String stripTo7Bits(String name) {
  448. final int length = name.length();
  449. StringBuilder result = new StringBuilder(length);
  450. for (int i = 0; i < length; i++) {
  451. char stripped = (char) (name.charAt(i) & 0x7F);
  452. if (stripped != 0) { // would be read as Trailing null
  453. result.append(stripped);
  454. }
  455. }
  456. return result.toString();
  457. }
  458. /**
  459. * Write an EOF (end of archive) record to the tar archive.
  460. * An EOF record consists of a record of all zeros.
  461. */
  462. private void writeEOFRecord() throws IOException {
  463. for (int i = 0; i < recordBuf.length; ++i) {
  464. recordBuf[i] = 0;
  465. }
  466. buffer.writeRecord(recordBuf);
  467. }
  468. private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
  469. TarEntry entry) {
  470. addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
  471. TarConstants.MAXSIZE);
  472. addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getLongGroupId(),
  473. TarConstants.MAXID);
  474. addPaxHeaderForBigNumber(paxHeaders, "mtime",
  475. entry.getModTime().getTime() / 1000,
  476. TarConstants.MAXSIZE);
  477. addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getLongUserId(),
  478. TarConstants.MAXID);
  479. // star extensions by J\u00f6rg Schilling
  480. addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
  481. entry.getDevMajor(), TarConstants.MAXID);
  482. addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
  483. entry.getDevMinor(), TarConstants.MAXID);
  484. // there is no PAX header for file mode
  485. failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
  486. }
  487. private void addPaxHeaderForBigNumber(Map<String, String> paxHeaders,
  488. String header, long value,
  489. long maxValue) {
  490. if (value < 0 || value > maxValue) {
  491. paxHeaders.put(header, String.valueOf(value));
  492. }
  493. }
  494. private void failForBigNumbers(TarEntry entry) {
  495. failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
  496. failForBigNumberWithPosixMessage("group id", entry.getLongGroupId(), TarConstants.MAXID);
  497. failForBigNumber("last modification time",
  498. entry.getModTime().getTime() / 1000,
  499. TarConstants.MAXSIZE);
  500. failForBigNumber("user id", entry.getLongUserId(), TarConstants.MAXID);
  501. failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
  502. failForBigNumber("major device number", entry.getDevMajor(),
  503. TarConstants.MAXID);
  504. failForBigNumber("minor device number", entry.getDevMinor(),
  505. TarConstants.MAXID);
  506. }
  507. private void failForBigNumber(String field, long value, long maxValue) {
  508. failForBigNumber(field, value, maxValue, "");
  509. }
  510. private void failForBigNumberWithPosixMessage(String field, long value, long maxValue) {
  511. failForBigNumber(field, value, maxValue, " Use STAR or POSIX extensions to overcome this limit");
  512. }
  513. private void failForBigNumber(String field, long value, long maxValue, String additionalMsg) {
  514. if (value < 0 || value > maxValue) {
  515. throw new RuntimeException(field + " '" + value
  516. + "' is too big ( > "
  517. + maxValue + " )");
  518. }
  519. }
  520. /**
  521. * Handles long file or link names according to the longFileMode setting.
  522. *
  523. * <p>I.e. if the given name is too long to be written to a plain
  524. * tar header then
  525. * <ul>
  526. * <li>it creates a pax header who's name is given by the
  527. * paxHeaderName parameter if longFileMode is POSIX</li>
  528. * <li>it creates a GNU longlink entry who's type is given by
  529. * the linkType parameter if longFileMode is GNU</li>
  530. * <li>it throws an exception if longFileMode is ERROR</li>
  531. * <li>it truncates the name if longFileMode is TRUNCATE</li>
  532. * </ul></p>
  533. *
  534. * @param entry entry the name belongs to
  535. * @param name the name to write
  536. * @param paxHeaders current map of pax headers
  537. * @param paxHeaderName name of the pax header to write
  538. * @param linkType type of the GNU entry to write
  539. * @param fieldName the name of the field
  540. * @return whether a pax header has been written.
  541. */
  542. private boolean handleLongName(TarEntry entry , String name,
  543. Map<String, String> paxHeaders,
  544. String paxHeaderName, byte linkType, String fieldName)
  545. throws IOException {
  546. final ByteBuffer encodedName = encoding.encode(name);
  547. final int len = encodedName.limit() - encodedName.position();
  548. if (len >= TarConstants.NAMELEN) {
  549. if (longFileMode == LONGFILE_POSIX) {
  550. paxHeaders.put(paxHeaderName, name);
  551. return true;
  552. } else if (longFileMode == LONGFILE_GNU) {
  553. // create a TarEntry for the LongLink, the contents
  554. // of which are the link's name
  555. TarEntry longLinkEntry =
  556. new TarEntry(TarConstants.GNU_LONGLINK, linkType);
  557. longLinkEntry.setSize(len + 1); // +1 for NUL
  558. transferModTime(entry, longLinkEntry);
  559. putNextEntry(longLinkEntry);
  560. write(encodedName.array(), encodedName.arrayOffset(), len);
  561. write(0); // NUL terminator
  562. closeEntry();
  563. } else if (longFileMode != LONGFILE_TRUNCATE) {
  564. throw new RuntimeException(fieldName + " '" + name
  565. + "' is too long ( > "
  566. + TarConstants.NAMELEN + " bytes)");
  567. }
  568. }
  569. return false;
  570. }
  571. private void transferModTime(TarEntry from, TarEntry to) {
  572. Date fromModTime = from.getModTime();
  573. long fromModTimeSeconds = fromModTime.getTime() / 1000;
  574. if (fromModTimeSeconds < 0 || fromModTimeSeconds > TarConstants.MAXSIZE) {
  575. fromModTime = new Date(0);
  576. }
  577. to.setModTime(fromModTime);
  578. }
  579. }