Bug 25704 - Support for Sun Solaris' Tar Format for long file name
Summary: Support for Sun Solaris' Tar Format for long file name
Status: RESOLVED WONTFIX
Alias: None
Product: Ant
Classification: Unclassified
Component: Core tasks (show other bugs)
Version: 1.6.0
Hardware: All All
: P3 enhancement with 2 votes (vote)
Target Milestone: ---
Assignee: Ant Notifications List
URL:
Keywords: PatchAvailable
Depends on:
Blocks:
 
Reported: 2003-12-22 17:38 UTC by Jack Chen
Modified: 2010-10-26 06:08 UTC (History)
0 users



Attachments
Src which been modified to fix tar task to surport Sun tar. Base on ant version 1.6.5. (24.75 KB, application/zip)
2006-08-30 01:10 UTC, Wang Liang
Details
Here is the compare report, left side is the file which been modified. (12.65 KB, application/zip)
2006-08-30 01:12 UTC, Wang Liang
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jack Chen 2003-12-22 17:38:50 UTC
When file name is more than 100 characters, Sun splits the name and the path and
stores the file name in the first 100 characters of the header block and the
directory name of up to 155 characters at the end of the header block.

I modified ANT 1.6.1 to support Sun Solaris' Tar format. Please incorporate the
changes to the future release of ANT. Let me know if you need further
information at chenyj@yahoo.com.



Jack Chen

/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.tools.ant.taskdefs;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.MergingMapper;
import org.apache.tools.ant.util.SourceFileScanner;
import org.apache.tools.bzip2.CBZip2OutputStream;
import org.apache.tools.tar.TarConstants;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarOutputStream;
import org.apache.tools.zip.UnixStat;

/**
 * Creates a tar archive.
 *
 * @author Stefano Mazzocchi
 *         <a href="mailto:stefano@apache.org">stefano@apache.org</a>
 * @author Stefan Bodewig
 * @author Magesh Umasankar
 *
 * @since Ant 1.1
 *
 * @ant.task category="packaging"
 */
public class Tar extends MatchingTask {

    /**
     * @deprecated Tar.WARN is deprecated and is replaced with
     *             Tar.TarLongFileMode.WARN
     */
    public static final String WARN = "warn";
    /**
     * @deprecated Tar.FAIL is deprecated and is replaced with
     *             Tar.TarLongFileMode.FAIL
     */
    public static final String FAIL = "fail";
    /**
     * @deprecated Tar.TRUNCATE is deprecated and is replaced with
     *             Tar.TarLongFileMode.TRUNCATE
     */
    public static final String TRUNCATE = "truncate";
    /**
     * @deprecated Tar.GNU is deprecated and is replaced with
     *             Tar.TarLongFileMode.GNU
     */
    public static final String GNU = "gnu";
    /**
     * @deprecated Tar.OMIT is deprecated and is replaced with
     *             Tar.TarLongFileMode.OMIT
     */
    public static final String OMIT = "omit";

    /**
     * @ Tar.SUN for Solaris Tar
     * jyc
     */
    public static final String SUN = "sun";
	public static final int SUN_MAXLEN = 255;

    File tarFile;
    File baseDir;

    private TarLongFileMode longFileMode = new TarLongFileMode();

    Vector filesets = new Vector();
    Vector fileSetFiles = new Vector();

    /**
     * Indicates whether the user has been warned about long files already.
     */
    private boolean longWarningGiven = false;

    private TarCompressionMethod compression = new TarCompressionMethod();

    /**
     * Add a new fileset with the option to specify permissions
     */
    public TarFileSet createTarFileSet() {
        TarFileSet fileset = new TarFileSet();
        filesets.addElement(fileset);
        return fileset;
    }


    /**
     * Set is the name/location of where to create the tar file.
     * @deprecated for consistency with other tasks, please use setDestFile()
     */
    public void setTarfile(File tarFile) {
        this.tarFile = tarFile;
    }

    /**
     * Set is the name/location of where to create the tar file.
     * @since Ant 1.5
     * @param destFile The output of the tar
     */
    public void setDestFile(File destFile) {
        this.tarFile = destFile;
    }

    /**
     * This is the base directory to look in for things to tar.
     */
    public void setBasedir(File baseDir) {
        this.baseDir = baseDir;
    }

    /**
     * Set how to handle long files, those with a path&gt;100 chars.
     * Optional, default=warn.
     * <p>
     * Allowable values are
     * <ul>
     * <li>  truncate - paths are truncated to the maximum length
     * <li>  fail - paths greater than the maximum cause a build exception
     * <li>  warn - paths greater than the maximum cause a warning and GNU is used
     * <li>  gnu - GNU extensions are used for any paths greater than the maximum.
     * <li>  omit - paths greater than the maximum are omitted from the archive
     * </ul>
     * @deprecated setLongFile(String) is deprecated and is replaced with
     *             setLongFile(Tar.TarLongFileMode) to make Ant's Introspection
     *             mechanism do the work and also to encapsulate operations on
     *             the mode in its own class.
     */
    public void setLongfile(String mode) {
        log("DEPRECATED - The setLongfile(String) method has been deprecated."
            + " Use setLongfile(Tar.TarLongFileMode) instead.");
        this.longFileMode = new TarLongFileMode();
        longFileMode.setValue(mode);
    }

    /**
     * Set how to handle long files, those with a path&gt;100 chars.
     * Optional, default=warn.
     * <p>
     * Allowable values are
     * <ul>
     * <li>  truncate - paths are truncated to the maximum length
     * <li>  fail - paths greater than the maximum cause a build exception
     * <li>  warn - paths greater than the maximum cause a warning and GNU is used
     * <li>  gnu - GNU extensions are used for any paths greater than the maximum.
     * <li>  omit - paths greater than the maximum are omitted from the archive
     * </ul>
     */
    public void setLongfile(TarLongFileMode mode) {
        this.longFileMode = mode;
    }

    /**
     * Set compression method.
     * Allowable values are
     * <ul>
     * <li>  none - no compression
     * <li>  gzip - Gzip compression
     * <li>  bzip2 - Bzip2 compression
     * </ul>
     */
    public void setCompression(TarCompressionMethod mode) {
        this.compression = mode;
    }

    /**
     * do the business
     */
    public void execute() throws BuildException {
        if (tarFile == null) {
            throw new BuildException("tarfile attribute must be set!",
                                     getLocation());
        }

        if (tarFile.exists() && tarFile.isDirectory()) {
            throw new BuildException("tarfile is a directory!",
                                     getLocation());
        }

        if (tarFile.exists() && !tarFile.canWrite()) {
            throw new BuildException("Can not write to the specified tarfile!",
                                     getLocation());
        }

        Vector savedFileSets = (Vector) filesets.clone();
        try {
            if (baseDir != null) {
                if (!baseDir.exists()) {
                    throw new BuildException("basedir does not exist!",
                                             getLocation());
                }

                // add the main fileset to the list of filesets to process.
                TarFileSet mainFileSet = new TarFileSet(fileset);
                mainFileSet.setDir(baseDir);
                filesets.addElement(mainFileSet);
            }

            if (filesets.size() == 0) {
                throw new BuildException("You must supply either a basedir "
                                         + "attribute or some nested filesets.",
                                         getLocation());
            }

            // check if tar is out of date with respect to each
            // fileset
            boolean upToDate = true;
            for (Enumeration e = filesets.elements(); e.hasMoreElements();) {
                TarFileSet fs = (TarFileSet) e.nextElement();
                String[] files = fs.getFiles(getProject());

                if (!archiveIsUpToDate(files, fs.getDir(getProject()))) {
                    upToDate = false;
                }

                for (int i = 0; i < files.length; ++i) {
                    if (tarFile.equals(new File(fs.getDir(getProject()),
                                                files[i]))) {
                        throw new BuildException("A tar file cannot include "
                                                 + "itself", getLocation());
                    }
                }
            }

            if (upToDate) {
                log("Nothing to do: " + tarFile.getAbsolutePath()
                    + " is up to date.", Project.MSG_INFO);
                return;
            }

            log("Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO);

            TarOutputStream tOut = null;
            try {
                tOut = new TarOutputStream(
                    compression.compress(
                        new BufferedOutputStream(
                            new FileOutputStream(tarFile))));
                tOut.setDebug(true);
                if (longFileMode.isTruncateMode()) {
                    tOut.setLongFileMode(TarOutputStream.LONGFILE_TRUNCATE);
                } else if (longFileMode.isFailMode()
                            || longFileMode.isOmitMode()) {
                    tOut.setLongFileMode(TarOutputStream.LONGFILE_ERROR);
                } else {
                    // warn or GNU
                    tOut.setLongFileMode(TarOutputStream.LONGFILE_GNU);
                }

                longWarningGiven = false;
                for (Enumeration e = filesets.elements();
                     e.hasMoreElements();) {
                    TarFileSet fs = (TarFileSet) e.nextElement();
                    String[] files = fs.getFiles(getProject());
                    if (files.length > 1 && fs.getFullpath().length() > 0) {
                        throw new BuildException("fullpath attribute may only "
                                                 + "be specified for "
                                                 + "filesets that specify a "
                                                 + "single file.");
                    }
                    for (int i = 0; i < files.length; i++) {
                        File f = new File(fs.getDir(getProject()), files[i]);
                        String name = files[i].replace(File.separatorChar, '/');
                        tarFile(f, tOut, name, fs);
                    }
                }
            } catch (IOException ioe) {
                String msg = "Problem creating TAR: " + ioe.getMessage();
                throw new BuildException(msg, ioe, getLocation());
            } finally {
                if (tOut != null) {
                    try {
                        // close up
                        tOut.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        } finally {
            filesets = savedFileSets;
        }
    }

    /**
     * tar a file
     */
    protected void tarFile(File file, TarOutputStream tOut, String vPath,
                           TarFileSet tarFileSet)
        throws IOException {
        FileInputStream fIn = null;

        String fullpath = tarFileSet.getFullpath();
        if (fullpath.length() > 0) {
            vPath = fullpath;
        } else {
            // don't add "" to the archive
            if (vPath.length() <= 0) {
                return;
            }

            if (file.isDirectory() && !vPath.endsWith("/")) {
                vPath += "/";
            }

            String prefix = tarFileSet.getPrefix();
            // '/' is appended for compatibility with the zip task.
            if (prefix.length() > 0 && !prefix.endsWith("/")) {
                prefix = prefix + "/";
            }
            vPath = prefix + vPath;
        }

        if (vPath.startsWith("/") && !tarFileSet.getPreserveLeadingSlashes()) {
            int l = vPath.length();
            if (l <= 1) {
                // we would end up adding "" to the archive
                return;
            }
            vPath = vPath.substring(1, l);
        }

        try {
            if (vPath.length() >= TarConstants.NAMELEN) {
            	if (longFileMode.isSunMode() 
            	     && getFileName(vPath).length() < TarConstants.NAMELEN
            	     && vPath.length() < SUN_MAXLEN) {
                    log("Sun Solaris Taring: " + vPath + "  length=" +
vPath.length(), Project.MSG_INFO);
            	     	
            	} else if (longFileMode.isOmitMode()) {
                    log("Omitting: " + vPath, Project.MSG_INFO);
                    return;
                } else if (longFileMode.isWarnMode()) {
                    log("Entry: " + vPath + " longer than "
                        + TarConstants.NAMELEN + " characters.",
                        Project.MSG_WARN);
                    if (!longWarningGiven) {
                        log("Resulting tar file can only be processed "
                            + "successfully by GNU compatible tar commands",
                            Project.MSG_WARN);
                        longWarningGiven = true;
                    }
                } else if (longFileMode.isFailMode()) {
                    throw new BuildException("Entry: " + vPath
                        + " longer than " + TarConstants.NAMELEN
                        + "characters.", getLocation());
                }
            }

            TarEntry te = new TarEntry(vPath);
            if (longFileMode.isSunMode() && vPath.length() >
TarConstants.NAMELEN ) { //jyc
            	te.setName( getFileName(vPath) );
            	te.setPrefix( getDirectoryName(vPath) );
            }
            te.setModTime(file.lastModified());
            if (!file.isDirectory()) {
                te.setSize(file.length());
                te.setMode(tarFileSet.getMode());
            } else {
                te.setMode(tarFileSet.getDirMode());
            }
            te.setUserName(tarFileSet.getUserName());
            te.setGroupName(tarFileSet.getGroup());

            tOut.putNextEntry(te);

            if (!file.isDirectory()) {
                fIn = new FileInputStream(file);

                byte[] buffer = new byte[8 * 1024];
                int count = 0;
                do {
                    tOut.write(buffer, 0, count);
                    count = fIn.read(buffer, 0, buffer.length);
                } while (count != -1);
            }

            tOut.closeEntry();
        } finally {
            if (fIn != null) {
                fIn.close();
            }
        }
    }

    /**
     * @deprecated use the two-arg version instead.
     */
    protected boolean archiveIsUpToDate(String[] files) {
        return archiveIsUpToDate(files, baseDir);
    }

    /**
     * @since Ant 1.5.2
     */
    protected boolean archiveIsUpToDate(String[] files, File dir) {
        SourceFileScanner sfs = new SourceFileScanner(this);
        MergingMapper mm = new MergingMapper();
        mm.setTo(tarFile.getAbsolutePath());
        return sfs.restrict(files, dir, null, mm).length == 0;
    }

    /**
     * This is a FileSet with the option to specify permissions
     */
    public static class TarFileSet extends FileSet {
        private String[] files = null;

        private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM;
        private int dirMode = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM;

        private String userName = "";
        private String groupName = "";
        private String prefix = "";
        private String fullpath = "";
        private boolean preserveLeadingSlashes = false;

        public TarFileSet(FileSet fileset) {
            super(fileset);
        }

        public TarFileSet() {
            super();
        }

        /**
         *  Get a list of files and directories specified in the fileset.
         *  @return a list of file and directory names, relative to
         *    the baseDir for the project.
         */
        public String[] getFiles(Project p) {
            if (files == null) {
                DirectoryScanner ds = getDirectoryScanner(p);
                String[] directories = ds.getIncludedDirectories();
                String[] filesPerSe = ds.getIncludedFiles();
                files = new String [directories.length + filesPerSe.length];
                System.arraycopy(directories, 0, files, 0, directories.length);
                System.arraycopy(filesPerSe, 0, files, directories.length,
                        filesPerSe.length);
            }

            return files;
        }

        /**
         * A 3 digit octal string, specify the user, group and
         * other modes in the standard Unix fashion;
         * optional, default=0644
         */
        public void setMode(String octalString) {
            this.fileMode =
                UnixStat.FILE_FLAG | Integer.parseInt(octalString, 8);
        }

        public int getMode() {
            return fileMode;
        }

        /**
         * A 3 digit octal string, specify the user, group and
         * other modes in the standard Unix fashion;
         * optional, default=0755
         *
         * @since Ant 1.6
         */
        public void setDirMode(String octalString) {
            this.dirMode =
                UnixStat.DIR_FLAG | Integer.parseInt(octalString, 8);
        }

        /**
         * @since Ant 1.6
         */
        public int getDirMode() {
            return dirMode;
        }

        /**
         * The username for the tar entry
         * This is not the same as the UID, which is
         * not currently set by the task.
         */
        public void setUserName(String userName) {
            this.userName = userName;
        }

        public String getUserName() {
            return userName;
        }

        /**
         * The groupname for the tar entry; optional, default=""
         * This is not the same as the GID, which is
         * not currently set by the task.
         */
        public void setGroup(String groupName) {
            this.groupName = groupName;
        }

        public String getGroup() {
            return groupName;
        }

        /**
         * If the prefix attribute is set, all files in the fileset
         * are prefixed with that path in the archive.
         * optional.
         */
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }

        public String getPrefix() {
            return prefix;
        }

        /**
         * If the fullpath attribute is set, the file in the fileset
         * is written with that path in the archive. The prefix attribute,
         * if specified, is ignored. It is an error to have more than one file
specified in
         * such a fileset.
         */
        public void setFullpath(String fullpath) {
            this.fullpath = fullpath;
        }

        public String getFullpath() {
            return fullpath;
        }

        /**
         * Flag to indicates whether leading `/'s should
         * be preserved in the file names.
         * Optional, default is <code>false</code>.
         */
        public void setPreserveLeadingSlashes(boolean b) {
            this.preserveLeadingSlashes = b;
        }

        public boolean getPreserveLeadingSlashes() {
            return preserveLeadingSlashes;
        }
    }

    /**
     * Set of options for long file handling in the task.
     *
     * @author Magesh Umasankar
     */
    public static class TarLongFileMode extends EnumeratedAttribute {

        // permissible values for longfile attribute
        public static final String WARN = "warn";
        public static final String FAIL = "fail";
        public static final String TRUNCATE = "truncate";
        public static final String GNU = "gnu";
        public static final String OMIT = "omit";
        public static final String SUN = "sun";

        private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT, SUN};

        public TarLongFileMode() {
            super();
            setValue(WARN);
        }

        public String[] getValues() {
            return validModes;
        }

        public boolean isTruncateMode() {
            return TRUNCATE.equalsIgnoreCase(getValue());
        }

        public boolean isWarnMode() {
            return WARN.equalsIgnoreCase(getValue());
        }

        public boolean isGnuMode() {
            return GNU.equalsIgnoreCase(getValue());
        }

        public boolean isFailMode() {
            return FAIL.equalsIgnoreCase(getValue());
        }

        public boolean isOmitMode() {
            return OMIT.equalsIgnoreCase(getValue());
        }
        public boolean isSunMode() {
            return SUN.equalsIgnoreCase(getValue());
        }
    }

    /**
     * Valid Modes for Compression attribute to Tar Task
     *
     */
    public static final class TarCompressionMethod extends EnumeratedAttribute {

        // permissible values for compression attribute
        /**
         *    No compression
         */
        private static final String NONE = "none";
        /**
         *    GZIP compression
         */
        private static final String GZIP = "gzip";
        /**
         *    BZIP2 compression
         */
        private static final String BZIP2 = "bzip2";


        /**
         * Default constructor
         */
        public TarCompressionMethod() {
            super();
            setValue(NONE);
        }

        /**
         *  Get valid enumeration values.
         *  @return valid enumeration values
         */
        public String[] getValues() {
            return new String[] {NONE, GZIP, BZIP2 };
        }

        /**
         *  This method wraps the output stream with the
         *     corresponding compression method
         *
         *  @param ostream output stream
         *  @return output stream with on-the-fly compression
         *  @exception IOException thrown if file is not writable
         */
        private OutputStream compress(final OutputStream ostream)
            throws IOException {
            final String value = getValue();
            if (GZIP.equals(value)) {
                return new GZIPOutputStream(ostream);
            } else {
                if (BZIP2.equals(value)) {
                    ostream.write('B');
                    ostream.write('Z');
                    return new CBZip2OutputStream(ostream);
                }
            }
            return ostream;
        }
    }
    public String getFileName(String name) {
    	int start = name.lastIndexOf('/');
    	return name.substring( start + 1);
    }
    
    public String getDirectoryName(String name) {
    	int start = name.lastIndexOf('/');
    	return name.substring( 0, start);
    }
}



/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 * This package is based on the work done by Timothy Gerard Endres
 * (time@ice.com) to whom the Ant project is very grateful for his great code.
 */

package org.apache.tools.tar;

import java.io.File;
import java.util.Date;
import java.util.Locale;

/**
 * This class represents an entry in a Tar archive. It consists
 * of the entry's header, as well as the entry's File. Entries
 * can be instantiated in one of three ways, depending on how
 * they are to be used.
 * <p>
 * TarEntries that are created from the header bytes read from
 * an archive are instantiated with the TarEntry( byte[] )
 * constructor. These entries will be used when extracting from
 * or listing the contents of an archive. These entries have their
 * header filled in using the header bytes. They also set the File
 * to null, since they reference an archive entry not a file.
 * <p>
 * TarEntries that are created from Files that are to be written
 * into an archive are instantiated with the TarEntry( File )
 * constructor. These entries have their header filled in using
 * the File's information. They also keep a reference to the File
 * for convenience when writing entries.
 * <p>
 * Finally, TarEntries can be constructed from nothing but a name.
 * This allows the programmer to construct the entry by hand, for
 * instance when only an InputStream is available for writing to
 * the archive, and the header information is constructed from
 * other information. In this case the header fields are set to
 * defaults and the File is set to null.
 *
 * <p>
 * The C structure for a Tar Entry's header is:
 * <pre>
 * struct header {
 * char name[NAMSIZ];
 * char mode[8];
 * char uid[8];
 * char gid[8];
 * char size[12];
 * char mtime[12];
 * char chksum[8];
 * char linkflag;
 * char linkname[NAMSIZ];
 * char magic[8];
 * char uname[TUNMLEN];
 * char gname[TGNMLEN];
 * char devmajor[8];
 * char devminor[8];
 * } header;
 * </pre>
 *
 * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
 * @author Stefano Mazzocchi <a
href="mailto:stefano@apache.org">stefano@apache.org</a>
 */

public class TarEntry implements TarConstants {
    /** The entry's name. */
    private StringBuffer name;

    /** The entry's permission mode. */
    private int mode;

    /** The entry's user id. */
    private int userId;

    /** The entry's group id. */
    private int groupId;

    /** The entry's size. */
    private long size;

    /** The entry's modification time. */
    private long modTime;

    /** The entry's checksum. */
    private int checkSum;

    /** The entry's link flag. */
    private byte linkFlag;

    /** The entry's link name. */
    private StringBuffer linkName;

    /** The entry's magic tag. */
    private StringBuffer magic;

    /** The entry's user name. */
    private StringBuffer userName;

    /** The entry's group name. */
    private StringBuffer groupName;

    /** The entry's major device number. */
    private int devMajor;

    /** The entry's minor device number. */
    private int devMinor;

    /** The entry's directory prefix. */
    private StringBuffer prefix;

    /** The entry's file reference */
    private File file;

    /** Maximum length of a user's name in the tar file */
    public static final int MAX_NAMELEN = 31;

    /** Default permissions bits for directories */
    public static final int DEFAULT_DIR_MODE = 040755;

    /** Default permissions bits for files */
    public static final int DEFAULT_FILE_MODE = 0100644;

    /** Convert millis to seconds */
    public static final int MILLIS_PER_SECOND = 1000;

    /** Sun Solaris Tar Max Full Path Length */
    //public static final int SUN_MAXLEN = 255;

    /**
     * Construct an empty entry and prepares the header values.
     */
    private TarEntry () {
        this.magic = new StringBuffer(TMAGIC);
        this.name = new StringBuffer();
        this.linkName = new StringBuffer();

        String user = System.getProperty("user.name", "");

        if (user.length() > MAX_NAMELEN) {
            user = user.substring(0, MAX_NAMELEN);
        }

        this.userId = 0;
        this.groupId = 0;
        this.userName = new StringBuffer(user);
        this.groupName = new StringBuffer("");
        this.prefix = new StringBuffer("");//jyc
        this.file = null;
    }

    /**
     * Construct an entry with only a name. This allows the programmer
     * to construct the entry's header "by hand". File is set to null.
     *
     * @param name the entry name
     */
    public TarEntry(String name) {
        this();

        boolean isDir = name.endsWith("/");

        this.devMajor = 0;
        this.devMinor = 0;
        this.name = new StringBuffer(name);
        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
        this.userId = 0;
        this.groupId = 0;
        this.size = 0;
        this.checkSum = 0;
        this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
        this.linkName = new StringBuffer("");
        this.userName = new StringBuffer("");
        this.groupName = new StringBuffer("");
        this.devMajor = 0;
        this.devMinor = 0;
        this.prefix = new StringBuffer("");//jyc

    }

    /**
     * Construct an entry with a name an a link flag.
     *
     * @param name the entry name
     * @param linkFlag the entry link flag.
     */
    public TarEntry(String name, byte linkFlag) {
        this(name);
        this.linkFlag = linkFlag;
    }

    /**
     * Construct an entry for a file. File is set to file, and the
     * header is constructed from information from the file.
     *
     * @param file The file that the entry represents.
     */
    public TarEntry(File file) {
        this();

        this.file = file;

        String name = file.getPath();
        String osname = System.getProperty("os.name").toLowerCase(Locale.US);

        if (osname != null) {

            // Strip off drive letters!
            // REVIEW Would a better check be "(File.separator == '\')"?

            if (osname.startsWith("windows")) {
                if (name.length() > 2) {
                    char ch1 = name.charAt(0);
                    char ch2 = name.charAt(1);

                    if (ch2 == ':'
                            && ((ch1 >= 'a' && ch1 <= 'z')
                                || (ch1 >= 'A' && ch1 <= 'Z'))) {
                        name = name.substring(2);
                    }
                }
            } else if (osname.indexOf("netware") > -1) {
                int colon = name.indexOf(':');
                if (colon != -1) {
                    name = name.substring(colon + 1);
                }
            }
        }

        name = name.replace(File.separatorChar, '/');

        // No absolute pathnames
        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
        // so we loop on starting /'s.
        while (name.startsWith("/")) {
            name = name.substring(1);
        }

        this.linkName = new StringBuffer("");
        this.name = new StringBuffer(name);

        if (file.isDirectory()) {
            this.mode = DEFAULT_DIR_MODE;
            this.linkFlag = LF_DIR;

            if (this.name.charAt(this.name.length() - 1) != '/') {
                this.name.append("/");
            }
        } else {
            this.mode = DEFAULT_FILE_MODE;
            this.linkFlag = LF_NORMAL;
        }

        this.size = file.length();
        this.modTime = file.lastModified() / MILLIS_PER_SECOND;
        this.checkSum = 0;
        this.devMajor = 0;
        this.devMinor = 0;
    }

    /**
     * Construct an entry from an archive's header bytes. File is set
     * to null.
     *
     * @param headerBuf The header bytes from a tar archive entry.
     */
    public TarEntry(byte[] headerBuf) {
        this();
        this.parseTarHeader(headerBuf);
    }

    /**
     * Determine if the two entries are equal. Equality is determined
     * by the header names being equal.
     *
     * @param it Entry to be checked for equality.
     * @return True if the entries are equal.
     */
    public boolean equals(TarEntry it) {
        return this.getName().equals(it.getName());
    }

    /**
     * Determine if the two entries are equal. Equality is determined
     * by the header names being equal.
     *
     * @param it Entry to be checked for equality.
     * @return True if the entries are equal.
     */
    public boolean equals(Object it) {
        if (it == null || getClass() != it.getClass()) {
            return false;
        }
        return equals((TarEntry) it);
    }

    /**
     * Hashcodes are based on entry names.
     *
     * @return the entry hashcode
     */
    public int hashCode() {
        return getName().hashCode();
    }

    /**
     * Determine if the given entry is a descendant of this entry.
     * Descendancy is determined by the name of the descendant
     * starting with this entry's name.
     *
     * @param desc Entry to be checked as a descendent of this.
     * @return True if entry is a descendant of this.
     */
    public boolean isDescendent(TarEntry desc) {
        return desc.getName().startsWith(this.getName());
    }

    /**
     * Get this entry's prefix.
     *
     * @return This entry's prefix.
     */
    public String getPrefix() {
        return this.prefix.toString();
    }

    /**
     * Set this entry's prefix.
     *
     * @param name This entry's new prefix.
     */
    public void setPrefix(String prefix) {
        this.prefix = new StringBuffer(prefix);
    }

    /**
     * Get this entry's name.
     *
     * @return This entry's name.
     */
    public String getName() {
        return this.name.toString();
    }

    /**
     * Set this entry's name.
     *
     * @param name This entry's new name.
     */
    public void setName(String name) {
        this.name = new StringBuffer(name);
    }

    /**
     * Set the mode for this entry
     *
     * @param mode the mode for this entry
     */
    public void setMode(int mode) {
        this.mode = mode;
    }

    /**
     * Get this entry's link name.
     *
     * @return This entry's link name.
     */
    public String getLinkName() {
        return this.linkName.toString();
    }

    /**
     * Get this entry's user id.
     *
     * @return This entry's user id.
     */
    public int getUserId() {
        return this.userId;
    }

    /**
     * Set this entry's user id.
     *
     * @param userId This entry's new user id.
     */
    public void setUserId(int userId) {
        this.userId = userId;
    }

    /**
     * Get this entry's group id.
     *
     * @return This entry's group id.
     */
    public int getGroupId() {
        return this.groupId;
    }

    /**
     * Set this entry's group id.
     *
     * @param groupId This entry's new group id.
     */
    public void setGroupId(int groupId) {
        this.groupId = groupId;
    }

    /**
     * Get this entry's user name.
     *
     * @return This entry's user name.
     */
    public String getUserName() {
        return this.userName.toString();
    }

    /**
     * Set this entry's user name.
     *
     * @param userName This entry's new user name.
     */
    public void setUserName(String userName) {
        this.userName = new StringBuffer(userName);
    }

    /**
     * Get this entry's group name.
     *
     * @return This entry's group name.
     */
    public String getGroupName() {
        return this.groupName.toString();
    }

    /**
     * Set this entry's group name.
     *
     * @param groupName This entry's new group name.
     */
    public void setGroupName(String groupName) {
        this.groupName = new StringBuffer(groupName);
    }

    /**
     * Convenience method to set this entry's group and user ids.
     *
     * @param userId This entry's new user id.
     * @param groupId This entry's new group id.
     */
    public void setIds(int userId, int groupId) {
        this.setUserId(userId);
        this.setGroupId(groupId);
    }

    /**
     * Convenience method to set this entry's group and user names.
     *
     * @param userName This entry's new user name.
     * @param groupName This entry's new group name.
     */
    public void setNames(String userName, String groupName) {
        this.setUserName(userName);
        this.setGroupName(groupName);
    }

    /**
     * Set this entry's modification time. The parameter passed
     * to this method is in "Java time".
     *
     * @param time This entry's new modification time.
     */
    public void setModTime(long time) {
        this.modTime = time / MILLIS_PER_SECOND;
    }

    /**
     * Set this entry's modification time.
     *
     * @param time This entry's new modification time.
     */
    public void setModTime(Date time) {
        this.modTime = time.getTime() / MILLIS_PER_SECOND;
    }

    /**
     * Set this entry's modification time.
     *
     * @return time This entry's new modification time.
     */
    public Date getModTime() {
        return new Date(this.modTime * MILLIS_PER_SECOND);
    }

    /**
     * Get this entry's file.
     *
     * @return This entry's file.
     */
    public File getFile() {
        return this.file;
    }

    /**
     * Get this entry's mode.
     *
     * @return This entry's mode.
     */
    public int getMode() {
        return this.mode;
    }

    /**
     * Get this entry's file size.
     *
     * @return This entry's file size.
     */
    public long getSize() {
        return this.size;
    }

    /**
     * Set this entry's file size.
     *
     * @param size This entry's new file size.
     */
    public void setSize(long size) {
        this.size = size;
    }


    /**
     * Indicate if this entry is a GNU long name block
     *
     * @return true if this is a long name extension provided by GNU tar
     */
    public boolean isGNULongNameEntry() {
        return linkFlag == LF_GNUTYPE_LONGNAME
                           && name.toString().equals(GNU_LONGLINK);
    }

    /**
     * Return whether or not this entry represents a directory.
     *
     * @return True if this entry is a directory.
     */
    public boolean isDirectory() {
        if (this.file != null) {
            return this.file.isDirectory();
        }

        if (this.linkFlag == LF_DIR) {
            return true;
        }

        if (this.getName().endsWith("/")) {
            return true;
        }

        return false;
    }

    /**
     * If this entry represents a file, and the file is a directory, return
     * an array of TarEntries for this entry's children.
     *
     * @return An array of TarEntry's for this entry's children.
     */
    public TarEntry[] getDirectoryEntries() {
        if (this.file == null || !this.file.isDirectory()) {
            return new TarEntry[0];
        }

        String[]   list = this.file.list();
        TarEntry[] result = new TarEntry[list.length];

        for (int i = 0; i < list.length; ++i) {
            result[i] = new TarEntry(new File(this.file, list[i]));
        }

        return result;
    }

    /**
     * Write an entry's header information to a header buffer.
     *
     * @param outbuf The tar entry header buffer to fill in.
     */
    public void writeEntryHeader(byte[] outbuf) {
        int offset = 0;

        offset = TarUtils.getNameBytes(this.name, outbuf, offset, NAMELEN);
        offset = TarUtils.getOctalBytes(this.mode, outbuf, offset, MODELEN);
        offset = TarUtils.getOctalBytes(this.userId, outbuf, offset, UIDLEN);
        offset = TarUtils.getOctalBytes(this.groupId, outbuf, offset, GIDLEN);
        offset = TarUtils.getLongOctalBytes(this.size, outbuf, offset, SIZELEN);
        offset = TarUtils.getLongOctalBytes(this.modTime, outbuf, offset,
MODTIMELEN);

        int csOffset = offset;

        for (int c = 0; c < CHKSUMLEN; ++c) {
            outbuf[offset++] = (byte) ' ';
        }

        outbuf[offset++] = this.linkFlag;
        offset = TarUtils.getNameBytes(this.linkName, outbuf, offset, NAMELEN);
        offset = TarUtils.getNameBytes(this.magic, outbuf, offset, MAGICLEN);
        offset = TarUtils.getNameBytes(this.userName, outbuf, offset, UNAMELEN);
        offset = TarUtils.getNameBytes(this.groupName, outbuf, offset, GNAMELEN);
        offset = TarUtils.getOctalBytes(this.devMajor, outbuf, offset, DEVLEN);
        offset = TarUtils.getOctalBytes(this.devMinor, outbuf, offset, DEVLEN);
        offset = TarUtils.getNameBytes(this.prefix, outbuf, offset,
outbuf.length - offset);//jyc

        while (offset < outbuf.length) {
            outbuf[offset++] = 0;
        }

        long checkSum = TarUtils.computeCheckSum(outbuf);

        TarUtils.getCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN);
    }

    /**
     * Parse an entry's header information from a header buffer.
     *
     * @param header The tar entry header buffer to get information from.
     */
    public void parseTarHeader(byte[] header) {
        int offset = 0;

        this.name = TarUtils.parseName(header, offset, NAMELEN);
        offset += NAMELEN;
        this.mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
        offset += MODELEN;
        this.userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
        offset += UIDLEN;
        this.groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
        offset += GIDLEN;
        this.size = TarUtils.parseOctal(header, offset, SIZELEN);
        offset += SIZELEN;
        this.modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
        offset += MODTIMELEN;
        this.checkSum = (int) TarUtils.parseOctal(header, offset, CHKSUMLEN);
        offset += CHKSUMLEN;
        this.linkFlag = header[offset++];
        this.linkName = TarUtils.parseName(header, offset, NAMELEN);
        offset += NAMELEN;
        this.magic = TarUtils.parseName(header, offset, MAGICLEN);
        offset += MAGICLEN;
        this.userName = TarUtils.parseName(header, offset, UNAMELEN);
        offset += UNAMELEN;
        this.groupName = TarUtils.parseName(header, offset, GNAMELEN);
        offset += GNAMELEN;
        this.devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
        offset += DEVLEN;
        this.devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
        offset += DEVLEN;
        this.prefix = TarUtils.parseName(header, offset, header.length -
offset);//jyc
    }
    
}



/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 * This package is based on the work done by Timothy Gerard Endres
 * (time@ice.com) to whom the Ant project is very grateful for his great code.
 */

package org.apache.tools.tar;

import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException;

/**
 * The TarOutputStream writes a UNIX tar archive as an OutputStream.
 * Methods are provided to put entries, and then write their contents
 * by writing to this stream using write().
 *
 * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
 */
public class TarOutputStream extends FilterOutputStream {
    /** Fail if a long file name is required in the archive. */
    public static final int LONGFILE_ERROR = 0;

    /** Long paths will be truncated in the archive. */
    public static final int LONGFILE_TRUNCATE = 1;

    /** GNU tar extensions are used to store long file names in the archive. */
    public static final int LONGFILE_GNU = 2;

    /** SUN tar extensions are used to store long file names in the archive. */
    public static final int LONGFILE_SUN = 3;

    protected boolean   debug;
    protected int       currSize;
    protected int       currBytes;
    protected byte[]    oneBuf;
    protected byte[]    recordBuf;
    protected int       assemLen;
    protected byte[]    assemBuf;
    protected TarBuffer buffer;
    protected int       longFileMode = LONGFILE_ERROR;

    public TarOutputStream(OutputStream os) {
        this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
    }

    public TarOutputStream(OutputStream os, int blockSize) {
        this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
    }

    public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
        super(os);

        this.buffer = new TarBuffer(os, blockSize, recordSize);
        this.debug = false;
        this.assemLen = 0;
        this.assemBuf = new byte[recordSize];
        this.recordBuf = new byte[recordSize];
        this.oneBuf = new byte[1];
    }

    public void setLongFileMode(int longFileMode) {
        this.longFileMode = longFileMode;
    }


    /**
     * Sets the debugging flag.
     *
     * @param debugF True to turn on debugging.
     */
    public void setDebug(boolean debugF) {
        this.debug = debugF;
    }

    /**
     * Sets the debugging flag in this stream's TarBuffer.
     *
     * @param debug True to turn on debugging.
     */
    public void setBufferDebug(boolean debug) {
        this.buffer.setDebug(debug);
    }

    /**
     * Ends the TAR archive without closing the underlying OutputStream.
     * The result is that the EOF record of nulls is written.
     */
    public void finish() throws IOException {
        this.writeEOFRecord();
    }

    /**
     * Ends the TAR archive and closes the underlying OutputStream.
     * This means that finish() is called followed by calling the
     * TarBuffer's close().
     */
    public void close() throws IOException {
        this.finish();
        this.buffer.close();
    }

    /**
     * Get the record size being used by this stream's TarBuffer.
     *
     * @return The TarBuffer record size.
     */
    public int getRecordSize() {
        return this.buffer.getRecordSize();
    }

    /**
     * Put an entry on the output stream. This writes the entry's
     * header record and positions the output stream for writing
     * the contents of the entry. Once this method is called, the
     * stream is ready for calls to write() to write the entry's
     * contents. Once the contents are written, closeEntry()
     * <B>MUST</B> be called to ensure that all buffered data
     * is completely written to the output stream.
     *
     * @param entry The TarEntry to be written to the archive.
     */
    public void putNextEntry(TarEntry entry) throws IOException {
        if (entry.getName().length() >= TarConstants.NAMELEN) {

			if (longFileMode == LONGFILE_SUN) {
				//continue process  jyc

            } else if (longFileMode == LONGFILE_GNU) {
                // create a TarEntry for the LongLink, the contents
                // of which are the entry's name
                TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
                                                     
TarConstants.LF_GNUTYPE_LONGNAME);

                longLinkEntry.setSize(entry.getName().length() + 1);
                putNextEntry(longLinkEntry);
                write(entry.getName().getBytes());
                write(0);
                closeEntry();
            } else if (longFileMode != LONGFILE_TRUNCATE) {
                throw new RuntimeException("file name '" + entry.getName()
                                             + "' is too long ( > "
                                             + TarConstants.NAMELEN + " bytes)");
            }
        }

        entry.writeEntryHeader(this.recordBuf);
        this.buffer.writeRecord(this.recordBuf);

        this.currBytes = 0;

        if (entry.isDirectory()) {
            this.currSize = 0;
        } else {
            this.currSize = (int) entry.getSize();
        }
    }

    /**
     * Close an entry. This method MUST be called for all file
     * entries that contain data. The reason is that we must
     * buffer data written to the stream in order to satisfy
     * the buffer's record based writes. Thus, there may be
     * data fragments still being assembled that must be written
     * to the output stream before this entry is closed and the
     * next entry written.
     */
    public void closeEntry() throws IOException {
        if (this.assemLen > 0) {
            for (int i = this.assemLen; i < this.assemBuf.length; ++i) {
                this.assemBuf[i] = 0;
            }

            this.buffer.writeRecord(this.assemBuf);

            this.currBytes += this.assemLen;
            this.assemLen = 0;
        }

        if (this.currBytes < this.currSize) {
            throw new IOException("entry closed at '" + this.currBytes
                                  + "' before the '" + this.currSize
                                  + "' bytes specified in the header were written");
        }
    }

    /**
     * Writes a byte to the current tar archive entry.
     *
     * This method simply calls read( byte[], int, int ).
     *
     * @param b The byte written.
     */
    public void write(int b) throws IOException {
        this.oneBuf[0] = (byte) b;

        this.write(this.oneBuf, 0, 1);
    }

    /**
     * Writes bytes to the current tar archive entry.
     *
     * This method simply calls write( byte[], int, int ).
     *
     * @param wBuf The buffer to write to the archive.
     */
    public void write(byte[] wBuf) throws IOException {
        this.write(wBuf, 0, wBuf.length);
    }

    /**
     * Writes bytes to the current tar archive entry. This method
     * is aware of the current entry and will throw an exception if
     * you attempt to write bytes past the length specified for the
     * current entry. The method is also (painfully) aware of the
     * record buffering required by TarBuffer, and manages buffers
     * that are not a multiple of recordsize in length, including
     * assembling records from small buffers.
     *
     * @param wBuf The buffer to write to the archive.
     * @param wOffset The offset in the buffer from which to get bytes.
     * @param numToWrite The number of bytes to write.
     */
    public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
        if ((this.currBytes + numToWrite) > this.currSize) {
            throw new IOException("request to write '" + numToWrite
                                  + "' bytes exceeds size in header of '"
                                  + this.currSize + "' bytes");

            //
            // We have to deal with assembly!!!
            // The programmer can be writing little 32 byte chunks for all
            // we know, and we must assemble complete records for writing.
            // REVIEW Maybe this should be in TarBuffer? Could that help to
            // eliminate some of the buffer copying.
            //
        }

        if (this.assemLen > 0) {
            if ((this.assemLen + numToWrite) >= this.recordBuf.length) {
                int aLen = this.recordBuf.length - this.assemLen;

                System.arraycopy(this.assemBuf, 0, this.recordBuf, 0,
                                 this.assemLen);
                System.arraycopy(wBuf, wOffset, this.recordBuf,
                                 this.assemLen, aLen);
                this.buffer.writeRecord(this.recordBuf);

                this.currBytes += this.recordBuf.length;
                wOffset += aLen;
                numToWrite -= aLen;
                this.assemLen = 0;
            } else {
                System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
                                 numToWrite);

                wOffset += numToWrite;
                this.assemLen += numToWrite;
                numToWrite -= numToWrite;
            }
        }

        //
        // When we get here we have EITHER:
        // o An empty "assemble" buffer.
        // o No bytes to write (numToWrite == 0)
        //
        while (numToWrite > 0) {
            if (numToWrite < this.recordBuf.length) {
                System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
                                 numToWrite);

                this.assemLen += numToWrite;

                break;
            }

            this.buffer.writeRecord(wBuf, wOffset);

            int num = this.recordBuf.length;

            this.currBytes += num;
            numToWrite -= num;
            wOffset += num;
        }
    }

    /**
     * Write an EOF (end of archive) record to the tar archive.
     * An EOF record consists of a record of all zeros.
     */
    private void writeEOFRecord() throws IOException {
        for (int i = 0; i < this.recordBuf.length; ++i) {
            this.recordBuf[i] = 0;
        }

        this.buffer.writeRecord(this.recordBuf);
    }
}
Comment 1 Stefan Bodewig 2004-01-30 15:51:38 UTC
Could we get this contribution as a diff output against Anbt 1.6.0's code please?

It is very hard to spot the changes this way.
Comment 2 Wang Liang 2006-08-30 01:10:37 UTC
Created attachment 18765 [details]
Src which been modified to fix tar task to surport Sun tar. Base on ant version 1.6.5.

include java source file:
	org\apache\tools\ant\taskdefs\Tar.java
	org\apache\tools\tar\TarConstants.java
	org\apache\tools\tar\TarEntry.java
	org\apache\tools\tar\TarOutputStream.java
	org\apache\tools\tar\TarUtils.java


I test it on windows 2000 and sun solaris 9, java version is 1.4.2_08, it works
well.
Still exist problem is:
This patch cannot surport multi-byte file names well.
Such as GB2312 code, it can not surport the file names's "String" length over
50.
Comment 3 Wang Liang 2006-08-30 01:12:02 UTC
Created attachment 18766 [details]
Here is the compare report, left side is the file which been modified.

Here is the compare report, left side is the file which been modified.
Comment 4 Stefan Bodewig 2010-10-26 06:08:21 UTC
An improved version of Ant's tar classes is part of Apache Commons Compress - I've opened a ticket there https://issues.apache.org/jira/browse/COMPRESS-121

Once implemented it will become available to Ant via the compress antlib.