1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.BufferedOutputStream;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.FileOutputStream;
  22. import java.io.IOException;
  23. import java.io.OutputStream;
  24. import java.util.Enumeration;
  25. import java.util.Vector;
  26. import java.util.zip.GZIPOutputStream;
  27. import org.apache.tools.ant.BuildException;
  28. import org.apache.tools.ant.DirectoryScanner;
  29. import org.apache.tools.ant.Project;
  30. import org.apache.tools.ant.types.EnumeratedAttribute;
  31. import org.apache.tools.ant.types.FileSet;
  32. import org.apache.tools.ant.util.MergingMapper;
  33. import org.apache.tools.ant.util.SourceFileScanner;
  34. import org.apache.tools.bzip2.CBZip2OutputStream;
  35. import org.apache.tools.tar.TarConstants;
  36. import org.apache.tools.tar.TarEntry;
  37. import org.apache.tools.tar.TarOutputStream;
  38. import org.apache.tools.zip.UnixStat;
  39. /**
  40. * Creates a tar archive.
  41. *
  42. * @since Ant 1.1
  43. *
  44. * @ant.task category="packaging"
  45. */
  46. public class Tar extends MatchingTask {
  47. /**
  48. * @deprecated Tar.WARN is deprecated and is replaced with
  49. * Tar.TarLongFileMode.WARN
  50. */
  51. public static final String WARN = "warn";
  52. /**
  53. * @deprecated Tar.FAIL is deprecated and is replaced with
  54. * Tar.TarLongFileMode.FAIL
  55. */
  56. public static final String FAIL = "fail";
  57. /**
  58. * @deprecated Tar.TRUNCATE is deprecated and is replaced with
  59. * Tar.TarLongFileMode.TRUNCATE
  60. */
  61. public static final String TRUNCATE = "truncate";
  62. /**
  63. * @deprecated Tar.GNU is deprecated and is replaced with
  64. * Tar.TarLongFileMode.GNU
  65. */
  66. public static final String GNU = "gnu";
  67. /**
  68. * @deprecated Tar.OMIT is deprecated and is replaced with
  69. * Tar.TarLongFileMode.OMIT
  70. */
  71. public static final String OMIT = "omit";
  72. File tarFile;
  73. File baseDir;
  74. private TarLongFileMode longFileMode = new TarLongFileMode();
  75. Vector filesets = new Vector();
  76. Vector fileSetFiles = new Vector();
  77. /**
  78. * Indicates whether the user has been warned about long files already.
  79. */
  80. private boolean longWarningGiven = false;
  81. private TarCompressionMethod compression = new TarCompressionMethod();
  82. /**
  83. * Add a new fileset with the option to specify permissions
  84. * @return the tar fileset to be used as the nested element.
  85. */
  86. public TarFileSet createTarFileSet() {
  87. TarFileSet fileset = new TarFileSet();
  88. filesets.addElement(fileset);
  89. return fileset;
  90. }
  91. /**
  92. * Set is the name/location of where to create the tar file.
  93. * @param tarFile the location of the tar file.
  94. * @deprecated for consistency with other tasks, please use setDestFile()
  95. */
  96. public void setTarfile(File tarFile) {
  97. this.tarFile = tarFile;
  98. }
  99. /**
  100. * Set is the name/location of where to create the tar file.
  101. * @since Ant 1.5
  102. * @param destFile The output of the tar
  103. */
  104. public void setDestFile(File destFile) {
  105. this.tarFile = destFile;
  106. }
  107. /**
  108. * This is the base directory to look in for things to tar.
  109. * @param baseDir the base directory.
  110. */
  111. public void setBasedir(File baseDir) {
  112. this.baseDir = baseDir;
  113. }
  114. /**
  115. * Set how to handle long files, those with a path>100 chars.
  116. * Optional, default=warn.
  117. * <p>
  118. * Allowable values are
  119. * <ul>
  120. * <li> truncate - paths are truncated to the maximum length
  121. * <li> fail - paths greater than the maximum cause a build exception
  122. * <li> warn - paths greater than the maximum cause a warning and GNU is used
  123. * <li> gnu - GNU extensions are used for any paths greater than the maximum.
  124. * <li> omit - paths greater than the maximum are omitted from the archive
  125. * </ul>
  126. * @param mode the mode string to handle long files.
  127. * @deprecated setLongFile(String) is deprecated and is replaced with
  128. * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection
  129. * mechanism do the work and also to encapsulate operations on
  130. * the mode in its own class.
  131. */
  132. public void setLongfile(String mode) {
  133. log("DEPRECATED - The setLongfile(String) method has been deprecated."
  134. + " Use setLongfile(Tar.TarLongFileMode) instead.");
  135. this.longFileMode = new TarLongFileMode();
  136. longFileMode.setValue(mode);
  137. }
  138. /**
  139. * Set how to handle long files, those with a path>100 chars.
  140. * Optional, default=warn.
  141. * <p>
  142. * Allowable values are
  143. * <ul>
  144. * <li> truncate - paths are truncated to the maximum length
  145. * <li> fail - paths greater than the maximum cause a build exception
  146. * <li> warn - paths greater than the maximum cause a warning and GNU is used
  147. * <li> gnu - GNU extensions are used for any paths greater than the maximum.
  148. * <li> omit - paths greater than the maximum are omitted from the archive
  149. * </ul>
  150. * @param mode the mode to handle long file names.
  151. */
  152. public void setLongfile(TarLongFileMode mode) {
  153. this.longFileMode = mode;
  154. }
  155. /**
  156. * Set compression method.
  157. * Allowable values are
  158. * <ul>
  159. * <li> none - no compression
  160. * <li> gzip - Gzip compression
  161. * <li> bzip2 - Bzip2 compression
  162. * </ul>
  163. * @param mode the compression method.
  164. */
  165. public void setCompression(TarCompressionMethod mode) {
  166. this.compression = mode;
  167. }
  168. /**
  169. * do the business
  170. * @throws BuildException on error
  171. */
  172. public void execute() throws BuildException {
  173. if (tarFile == null) {
  174. throw new BuildException("tarfile attribute must be set!",
  175. getLocation());
  176. }
  177. if (tarFile.exists() && tarFile.isDirectory()) {
  178. throw new BuildException("tarfile is a directory!",
  179. getLocation());
  180. }
  181. if (tarFile.exists() && !tarFile.canWrite()) {
  182. throw new BuildException("Can not write to the specified tarfile!",
  183. getLocation());
  184. }
  185. Vector savedFileSets = (Vector) filesets.clone();
  186. try {
  187. if (baseDir != null) {
  188. if (!baseDir.exists()) {
  189. throw new BuildException("basedir does not exist!",
  190. getLocation());
  191. }
  192. // add the main fileset to the list of filesets to process.
  193. TarFileSet mainFileSet = new TarFileSet(fileset);
  194. mainFileSet.setDir(baseDir);
  195. filesets.addElement(mainFileSet);
  196. }
  197. if (filesets.size() == 0) {
  198. throw new BuildException("You must supply either a basedir "
  199. + "attribute or some nested filesets.",
  200. getLocation());
  201. }
  202. // check if tar is out of date with respect to each
  203. // fileset
  204. boolean upToDate = true;
  205. for (Enumeration e = filesets.elements(); e.hasMoreElements();) {
  206. TarFileSet fs = (TarFileSet) e.nextElement();
  207. String[] files = fs.getFiles(getProject());
  208. if (!archiveIsUpToDate(files, fs.getDir(getProject()))) {
  209. upToDate = false;
  210. }
  211. for (int i = 0; i < files.length; ++i) {
  212. if (tarFile.equals(new File(fs.getDir(getProject()),
  213. files[i]))) {
  214. throw new BuildException("A tar file cannot include "
  215. + "itself", getLocation());
  216. }
  217. }
  218. }
  219. if (upToDate) {
  220. log("Nothing to do: " + tarFile.getAbsolutePath()
  221. + " is up to date.", Project.MSG_INFO);
  222. return;
  223. }
  224. log("Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO);
  225. TarOutputStream tOut = null;
  226. try {
  227. tOut = new TarOutputStream(
  228. compression.compress(
  229. new BufferedOutputStream(
  230. new FileOutputStream(tarFile))));
  231. tOut.setDebug(true);
  232. if (longFileMode.isTruncateMode()) {
  233. tOut.setLongFileMode(TarOutputStream.LONGFILE_TRUNCATE);
  234. } else if (longFileMode.isFailMode()
  235. || longFileMode.isOmitMode()) {
  236. tOut.setLongFileMode(TarOutputStream.LONGFILE_ERROR);
  237. } else {
  238. // warn or GNU
  239. tOut.setLongFileMode(TarOutputStream.LONGFILE_GNU);
  240. }
  241. longWarningGiven = false;
  242. for (Enumeration e = filesets.elements();
  243. e.hasMoreElements();) {
  244. TarFileSet fs = (TarFileSet) e.nextElement();
  245. String[] files = fs.getFiles(getProject());
  246. if (files.length > 1 && fs.getFullpath().length() > 0) {
  247. throw new BuildException("fullpath attribute may only "
  248. + "be specified for "
  249. + "filesets that specify a "
  250. + "single file.");
  251. }
  252. for (int i = 0; i < files.length; i++) {
  253. File f = new File(fs.getDir(getProject()), files[i]);
  254. String name = files[i].replace(File.separatorChar, '/');
  255. tarFile(f, tOut, name, fs);
  256. }
  257. }
  258. } catch (IOException ioe) {
  259. String msg = "Problem creating TAR: " + ioe.getMessage();
  260. throw new BuildException(msg, ioe, getLocation());
  261. } finally {
  262. if (tOut != null) {
  263. try {
  264. // close up
  265. tOut.close();
  266. } catch (IOException e) {
  267. // ignore
  268. }
  269. }
  270. }
  271. } finally {
  272. filesets = savedFileSets;
  273. }
  274. }
  275. /**
  276. * tar a file
  277. * @param file the file to tar
  278. * @param tOut the output stream
  279. * @param vPath the path name of the file to tar
  280. * @param tarFileSet the fileset that the file came from.
  281. * @throws IOException on error
  282. */
  283. protected void tarFile(File file, TarOutputStream tOut, String vPath,
  284. TarFileSet tarFileSet)
  285. throws IOException {
  286. FileInputStream fIn = null;
  287. String fullpath = tarFileSet.getFullpath();
  288. if (fullpath.length() > 0) {
  289. vPath = fullpath;
  290. } else {
  291. // don't add "" to the archive
  292. if (vPath.length() <= 0) {
  293. return;
  294. }
  295. if (file.isDirectory() && !vPath.endsWith("/")) {
  296. vPath += "/";
  297. }
  298. String prefix = tarFileSet.getPrefix();
  299. // '/' is appended for compatibility with the zip task.
  300. if (prefix.length() > 0 && !prefix.endsWith("/")) {
  301. prefix = prefix + "/";
  302. }
  303. vPath = prefix + vPath;
  304. }
  305. if (vPath.startsWith("/") && !tarFileSet.getPreserveLeadingSlashes()) {
  306. int l = vPath.length();
  307. if (l <= 1) {
  308. // we would end up adding "" to the archive
  309. return;
  310. }
  311. vPath = vPath.substring(1, l);
  312. }
  313. try {
  314. if (vPath.length() >= TarConstants.NAMELEN) {
  315. if (longFileMode.isOmitMode()) {
  316. log("Omitting: " + vPath, Project.MSG_INFO);
  317. return;
  318. } else if (longFileMode.isWarnMode()) {
  319. log("Entry: " + vPath + " longer than "
  320. + TarConstants.NAMELEN + " characters.",
  321. Project.MSG_WARN);
  322. if (!longWarningGiven) {
  323. log("Resulting tar file can only be processed "
  324. + "successfully by GNU compatible tar commands",
  325. Project.MSG_WARN);
  326. longWarningGiven = true;
  327. }
  328. } else if (longFileMode.isFailMode()) {
  329. throw new BuildException("Entry: " + vPath
  330. + " longer than " + TarConstants.NAMELEN
  331. + "characters.", getLocation());
  332. }
  333. }
  334. TarEntry te = new TarEntry(vPath);
  335. te.setModTime(file.lastModified());
  336. if (!file.isDirectory()) {
  337. te.setSize(file.length());
  338. te.setMode(tarFileSet.getMode());
  339. } else {
  340. te.setMode(tarFileSet.getDirMode());
  341. }
  342. te.setUserName(tarFileSet.getUserName());
  343. te.setGroupName(tarFileSet.getGroup());
  344. te.setUserId(tarFileSet.getUid());
  345. te.setGroupId(tarFileSet.getGid());
  346. tOut.putNextEntry(te);
  347. if (!file.isDirectory()) {
  348. fIn = new FileInputStream(file);
  349. byte[] buffer = new byte[8 * 1024];
  350. int count = 0;
  351. do {
  352. tOut.write(buffer, 0, count);
  353. count = fIn.read(buffer, 0, buffer.length);
  354. } while (count != -1);
  355. }
  356. tOut.closeEntry();
  357. } finally {
  358. if (fIn != null) {
  359. fIn.close();
  360. }
  361. }
  362. }
  363. /**
  364. * Is the archive up to date in relationship to a list of files.
  365. * @param files the files to check
  366. * @return true if the archive is up to date.
  367. * @deprecated use the two-arg version instead.
  368. */
  369. protected boolean archiveIsUpToDate(String[] files) {
  370. return archiveIsUpToDate(files, baseDir);
  371. }
  372. /**
  373. * Is the archive up to date in relationship to a list of files.
  374. * @param files the files to check
  375. * @param dir the base directory for the files.
  376. * @return true if the archive is up to date.
  377. * @since Ant 1.5.2
  378. */
  379. protected boolean archiveIsUpToDate(String[] files, File dir) {
  380. SourceFileScanner sfs = new SourceFileScanner(this);
  381. MergingMapper mm = new MergingMapper();
  382. mm.setTo(tarFile.getAbsolutePath());
  383. return sfs.restrict(files, dir, null, mm).length == 0;
  384. }
  385. /**
  386. * This is a FileSet with the option to specify permissions
  387. * and other attributes.
  388. */
  389. public static class TarFileSet extends FileSet {
  390. private String[] files = null;
  391. private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM;
  392. private int dirMode = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM;
  393. private String userName = "";
  394. private String groupName = "";
  395. private int uid;
  396. private int gid;
  397. private String prefix = "";
  398. private String fullpath = "";
  399. private boolean preserveLeadingSlashes = false;
  400. /**
  401. * Creates a new <code>TarFileSet</code> instance.
  402. * Using a fileset as a constructor argument.
  403. *
  404. * @param fileset a <code>FileSet</code> value
  405. */
  406. public TarFileSet(FileSet fileset) {
  407. super(fileset);
  408. }
  409. /**
  410. * Creates a new <code>TarFileSet</code> instance.
  411. *
  412. */
  413. public TarFileSet() {
  414. super();
  415. }
  416. /**
  417. * Get a list of files and directories specified in the fileset.
  418. * @param p the current project.
  419. * @return a list of file and directory names, relative to
  420. * the baseDir for the project.
  421. */
  422. public String[] getFiles(Project p) {
  423. if (files == null) {
  424. DirectoryScanner ds = getDirectoryScanner(p);
  425. String[] directories = ds.getIncludedDirectories();
  426. String[] filesPerSe = ds.getIncludedFiles();
  427. files = new String [directories.length + filesPerSe.length];
  428. System.arraycopy(directories, 0, files, 0, directories.length);
  429. System.arraycopy(filesPerSe, 0, files, directories.length,
  430. filesPerSe.length);
  431. }
  432. return files;
  433. }
  434. /**
  435. * A 3 digit octal string, specify the user, group and
  436. * other modes in the standard Unix fashion;
  437. * optional, default=0644
  438. * @param octalString a 3 digit octal string.
  439. */
  440. public void setMode(String octalString) {
  441. this.fileMode =
  442. UnixStat.FILE_FLAG | Integer.parseInt(octalString, 8);
  443. }
  444. /**
  445. * @return the current mode.
  446. */
  447. public int getMode() {
  448. return fileMode;
  449. }
  450. /**
  451. * A 3 digit octal string, specify the user, group and
  452. * other modes in the standard Unix fashion;
  453. * optional, default=0755
  454. *
  455. * @param octalString a 3 digit octal string.
  456. * @since Ant 1.6
  457. */
  458. public void setDirMode(String octalString) {
  459. this.dirMode =
  460. UnixStat.DIR_FLAG | Integer.parseInt(octalString, 8);
  461. }
  462. /**
  463. * @return the current directory mode
  464. * @since Ant 1.6
  465. */
  466. public int getDirMode() {
  467. return dirMode;
  468. }
  469. /**
  470. * The username for the tar entry
  471. * This is not the same as the UID.
  472. * @param userName the user name for the tar entry.
  473. */
  474. public void setUserName(String userName) {
  475. this.userName = userName;
  476. }
  477. /**
  478. * @return the user name for the tar entry
  479. */
  480. public String getUserName() {
  481. return userName;
  482. }
  483. /**
  484. * The uid for the tar entry
  485. * This is not the same as the User name.
  486. * @param uid the id of the user for the tar entry.
  487. */
  488. public void setUid(int uid) {
  489. this.uid = uid;
  490. }
  491. /**
  492. * @return the uid for the tar entry
  493. */
  494. public int getUid() {
  495. return uid;
  496. }
  497. /**
  498. * The groupname for the tar entry; optional, default=""
  499. * This is not the same as the GID.
  500. * @param groupName the group name string.
  501. */
  502. public void setGroup(String groupName) {
  503. this.groupName = groupName;
  504. }
  505. /**
  506. * @return the group name string.
  507. */
  508. public String getGroup() {
  509. return groupName;
  510. }
  511. /**
  512. * The GID for the tar entry; optional, default="0"
  513. * This is not the same as the group name.
  514. * @param gid the group id.
  515. */
  516. public void setGid(int gid) {
  517. this.gid = gid;
  518. }
  519. /**
  520. * @return the group identifier.
  521. */
  522. public int getGid() {
  523. return gid;
  524. }
  525. /**
  526. * If the prefix attribute is set, all files in the fileset
  527. * are prefixed with that path in the archive.
  528. * optional.
  529. * @param prefix the path prefix.
  530. */
  531. public void setPrefix(String prefix) {
  532. this.prefix = prefix;
  533. }
  534. /**
  535. * @return the path prefix for the files in the fileset.
  536. */
  537. public String getPrefix() {
  538. return prefix;
  539. }
  540. /**
  541. * If the fullpath attribute is set, the file in the fileset
  542. * is written with that path in the archive. The prefix attribute,
  543. * if specified, is ignored. It is an error to have more than one file specified in
  544. * such a fileset.
  545. * @param fullpath the path to use for the file in a fileset.
  546. */
  547. public void setFullpath(String fullpath) {
  548. this.fullpath = fullpath;
  549. }
  550. /**
  551. * @return the path to use for a single file fileset.
  552. */
  553. public String getFullpath() {
  554. return fullpath;
  555. }
  556. /**
  557. * Flag to indicates whether leading `/'s should
  558. * be preserved in the file names.
  559. * Optional, default is <code>false</code>.
  560. * @param b the leading slashes flag.
  561. */
  562. public void setPreserveLeadingSlashes(boolean b) {
  563. this.preserveLeadingSlashes = b;
  564. }
  565. /**
  566. * @return the leading slashes flag.
  567. */
  568. public boolean getPreserveLeadingSlashes() {
  569. return preserveLeadingSlashes;
  570. }
  571. }
  572. /**
  573. * Set of options for long file handling in the task.
  574. *
  575. */
  576. public static class TarLongFileMode extends EnumeratedAttribute {
  577. /** permissible values for longfile attribute */
  578. public static final String
  579. WARN = "warn",
  580. FAIL = "fail",
  581. TRUNCATE = "truncate",
  582. GNU = "gnu",
  583. OMIT = "omit";
  584. private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT};
  585. /** Constructor, defaults to "warn" */
  586. public TarLongFileMode() {
  587. super();
  588. setValue(WARN);
  589. }
  590. /**
  591. * @return the possible values for this enumerated type.
  592. */
  593. public String[] getValues() {
  594. return validModes;
  595. }
  596. /**
  597. * @return true if value is "truncate".
  598. */
  599. public boolean isTruncateMode() {
  600. return TRUNCATE.equalsIgnoreCase(getValue());
  601. }
  602. /**
  603. * @return true if value is "warn".
  604. */
  605. public boolean isWarnMode() {
  606. return WARN.equalsIgnoreCase(getValue());
  607. }
  608. /**
  609. * @return true if value is "gnu".
  610. */
  611. public boolean isGnuMode() {
  612. return GNU.equalsIgnoreCase(getValue());
  613. }
  614. /**
  615. * @return true if value is "fail".
  616. */
  617. public boolean isFailMode() {
  618. return FAIL.equalsIgnoreCase(getValue());
  619. }
  620. /**
  621. * @return true if value is "omit".
  622. */
  623. public boolean isOmitMode() {
  624. return OMIT.equalsIgnoreCase(getValue());
  625. }
  626. }
  627. /**
  628. * Valid Modes for Compression attribute to Tar Task
  629. *
  630. */
  631. public static final class TarCompressionMethod extends EnumeratedAttribute {
  632. // permissible values for compression attribute
  633. /**
  634. * No compression
  635. */
  636. private static final String NONE = "none";
  637. /**
  638. * GZIP compression
  639. */
  640. private static final String GZIP = "gzip";
  641. /**
  642. * BZIP2 compression
  643. */
  644. private static final String BZIP2 = "bzip2";
  645. /**
  646. * Default constructor
  647. */
  648. public TarCompressionMethod() {
  649. super();
  650. setValue(NONE);
  651. }
  652. /**
  653. * Get valid enumeration values.
  654. * @return valid enumeration values
  655. */
  656. public String[] getValues() {
  657. return new String[] {NONE, GZIP, BZIP2 };
  658. }
  659. /**
  660. * This method wraps the output stream with the
  661. * corresponding compression method
  662. *
  663. * @param ostream output stream
  664. * @return output stream with on-the-fly compression
  665. * @exception IOException thrown if file is not writable
  666. */
  667. private OutputStream compress(final OutputStream ostream)
  668. throws IOException {
  669. final String value = getValue();
  670. if (GZIP.equals(value)) {
  671. return new GZIPOutputStream(ostream);
  672. } else {
  673. if (BZIP2.equals(value)) {
  674. ostream.write('B');
  675. ostream.write('Z');
  676. return new CBZip2OutputStream(ostream);
  677. }
  678. }
  679. return ostream;
  680. }
  681. }
  682. }