Package org.jnode.command.archive

Source Code of org.jnode.command.archive.TarCommand

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.archive;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.tools.bzip2.CBZip2InputStream;
import org.apache.tools.bzip2.CBZip2OutputStream;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarOutputStream;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.StringArgument;

/**
*
* This version of tar has the ability to magically work with compressed archives without being
* told with the -jz flags on the command line. The -jz flags are seen as an override to force tar
* to work with the selected compression method, and fail if it can't do that.
*
* Reading :
* 1) If the -j or -z flags are set, then tar will check for the magic bytes in the header for the
*    given compression method. If they are not found, tar will fail.
* 2) If the -j or -z flags are not set, then tar will check for the magic bytes in the header for
*    both compression methods. If either are found, tar will wrap the FileInputStream given to TarInputStream
*    with a decompression wrapper stream. If neither are found, then tar assumes the archive is an
*    uncompressed archive.
*    If the archive is being extracted from stdin, and the archive is compressed, then the -j or -z flags
*    must be given, or tar will assume it is already uncompressed.
*
* Creating :
* 1) If the -j or -z flags are set, then tar will output a new archive compressed with the given method.
* 2) If the -j or -z flags are not set, then tar will check the file suffix of the new archive. If the suffix
*    is either .tbz, .tbz2, .tar.bz or .tar.bz2, then tar will compress with bzip2. If the suffix is .tar.gz
*    then tar will compress with gzip. Otherwise an uncompressed archive is created.
*
* Writing :
* 1) If the -j or -z flags are set, then tar will output an archive compressed with the given method.
* 2) If the -j or -z flags are not set, and the original archive was compressed, then the same method will
*    be used to recompress the archive. Otherwise the archive will be output uncompressed.
*
* TODO Currently, in order to set the output stream to the last entry of an archive, in order to append new entries,
*      we're moving the archive, creating a new archive, and copying the data via streams. A more effecient method
*      would be a reverse search of the file looking for the last block to be written too. Blocks are written in
*      groups of 20, so it will be relatively fast to find. Once we read the header, we'll know how big the entry is
*      and we can position the OutputStream to the first available block. We can't simply append to the end of the
*      file because the last block is not necessarily the end of the file.
*      This might not be quite so simple though, especially when dealing with compressed archives. Which can't be
*      randomly accessed.
*
* TODO Implement a way to check if a file being handled is actually a tar file. The tar input stream will gladly
*      read a file that is not a tar archive. This can cause any sort of irrational behavior, especially if the
*      data is binary in nature.
* TODO Implement update/delete.
* TODO Implement interactive (global)
* TODO Implement display totals. (global)
* TODO Implement verification (verify)
* FIXME BUG - if extracting an archive given on stdin, and that archive overwrites files on the filesystem
*             it will cause openFileWrite() to prompt the user and ask to overwrite, but reading from stdin
*             wont come from the console since stdin is attached to a pipe. In this case we will either have
*             to default to overwrite or skip.
* TODO When extracting entries, tar needs to strip any leading slashes by default, turning absolute pathnames
*      into relative ones. When inserting entries, leading slashes should also be stripped by default. Also
*      warn about inserting entries with a '..' prefix, and refuse to extract them.
* TODO Default behavior when extracting is to overwrite.
* TODO Implement exclusion by date, --newer=<date> ignores files with a mod time older than the given date.
*
* TODO Options to implement
* -P --absolute-names [Create, Append, Update] TODO
*      Overrides the default behavior of stripping leading slashes while inserting or extracting entries. Will
*      also force tar to extract entries prefixed with '..'
* -N --newer [All] TODO
*      Limit operating on files to only those that are newer than the given date. If the value starts with a
*      / or ., the value is a file, and its mod time should be used.
* --newer-mtime [All] TODO
*      Similar to --newer, except it doesn't take into account status changes made to the file, only content
*      changes (might not be a difference atm in jnode, so they'll both be the same).
* --atime-preserve [Create, Append, Update, Extract]
*      Preserve the access time of the file. (Not important)
* -a --auto-compress [Create]
*      When creating an archive, determine the compression type from the archive suffix. We already do this by
*      default if no compress flags (j/z) were given.
* --no-auto-compress [Create] TODO
*      Turn off the automatic checking of archive suffix to determine compression type.
* --backup [Extract] TODO
*      Backup files instead of overwriting them. We currently implement simple backups using a given or default
*      suffix. There is also the option of creating numbered backups. And a hybrid option that makes numbered
*      backups if they exist already, simple backups if they do not. As well this option can also apply to the
*      archive itself if it is being modified. This option gets its default from the VERSION_CONTROL env variable.
*      If this is not set, the default is 'existing'.
*      Options are 't' | 'numbered', 'nil' | 'existing' and 'never' | 'simple'.
* --suffix [Extract/backup]
*      Need to read the SIMPLE_BACKUP_SUFFIX env variable for the default, otherwise use ~
* --checkpoint n
*      Checkpoints are issued every nth recorded that is written or read to/from the archive.
* --checkpoing-action <action>
*      Perform one of the following actions every checkpoint
*      - bell Produces an audible bell on the system speaker
*      - dot prints a '.' to stdout
*      - echo Display a message to stderr
*      - echo=string Display string to stderr, string is subject to meta-character expansion
*      - exec=command Execute the given command
*      - sleep=time Sleep for <time> seconds
*      - ttyout=string Output string to /dev/tty (the current console, if stderr is redirected)
*      This argument may be given multiple times to perform different actions. They will be executed in the
*      order found on the command line.
* -l --check-links
* --delay-directory-restore
* --no-delay-directory-restore
*      Restore directory timestamps and permissions after extraction has completed.
* -h --dereference
*      Store the file a link points to instead of the link itself
* --anchored
* --no-anchored
* --ignore-case
* --no-ignore-case
* --wildcards
* --no-wildcards
* --wildcards-match-slash
* --no-wildcards-match-slash
*      Pattern matching modifier
* --ignore-failed-read
*      Do not exit just because a read of a file failed. (We do this by default atm)
* --index-file=<file>
*      Send verbose output to <file>
* --overwrite
* --overwrite-dir
*      Overwrite existing files when extracting (default)
*
* --lzma
* --lzop
* -Z --compress
*      Other compression methods, not supported atm.
*
* --pax-option=<list>
* --owner=<name>
* -o --no-same-owner
* --no-same-permissions
* --no-unquote
* --numberic-owner
* --occurence=<number>
* --one-file-system
* --null
* --no-null
* --no-overwrite-dir
* --no-ignore-command-error
* -M --multi-volume
* --no-check-device
* -g --listed-incremental=<file>
* -V --label=<string>
* -F --info-script=<file>
* -G --incremental
* --group
* --owner
* --mode
* --mtime
* -H --format
* --interactive
* -R --block-number
* -b --blocking-factor
* -i --ignore-zeroes
* -B --read-full-records
* --check-device
*      (ignore for now)
*
* @author chris boertien
*/
public class TarCommand extends ArchiveCommand {
   
    private static final String help_append  = "append entries to an archive";
    private static final String help_concat  = "concatenate multiple archives";
    private static final String help_create  = "create a new tar archive";
    private static final String help_delete  = "delete entries from an archive";
    private static final String help_diff    = "find differences between the archive and file system";
    private static final String help_extract = "extract the entries from an archive";
    private static final String help_list    = "list the contents of an archive";
    private static final String help_update  = "only append files that are newer than the copy in the archive";
   
    private static final String help_archive   = "use the given archive";
    private static final String help_backup    = "backup files instead of overwriting";
    private static final String help_bzip      = "compress the archive with bzip2";
    private static final String help_dir       = "change to directory";
    private static final String help_exclude   = "exclude files matching <pattern>";
    private static final String help_file_list = "get names to extract or archive from <file>";
    private static final String help_gzip      = "compress the archive with gzip";
    private static final String help_interact  = "ask for confirmation for every action";
    private static final String help_keep_old  = "keep existing files; don't overwrite from archive";
    private static final String help_keep_new  = "keep existing files if they are newer than the archive entry";
    private static final String help_norecurse = "do not recurse into directories";
    private static final String help_paths     = "files and directories to include in archive";
    private static final String help_recurse   = "recurse into directories";
    private static final String help_remove    = "remove files after adding them to the archive";
    @SuppressWarnings("unused")
    private static final String help_stdout    = "extract files to stdout";
    private static final String help_suffix    = "append <suffix> to backup files (default ~)";
    private static final String help_totals    = "display total bytes written after creating the archive";
    private static final String help_unlink    = "when extracting, delete files if they exist. This is the default" +
                                                 "action and is used to override other options if they were set";
    @SuppressWarnings("unused")
    private static final String help_verbose   = "list files processed";
    private static final String help_verify    = "verify the archive after writing it";
    private static final String help_xfile     = "exclude files matching patterns in <file>";
   
    private static final String err_options    = "required options -Acdtrux not found, or multiple options set";
   
    private static final int TAR_APPEND     = 0x01;
    private static final int TAR_CREATE     = 0x02;
    private static final int TAR_CONCAT     = 0x04;
    private static final int TAR_DELETE     = 0x08;
    private static final int TAR_UPDATE     = 0x10;
    private static final int TAR_LIST       = 0x20;
    private static final int TAR_DIFF       = 0x40;
    private static final int TAR_EXTRACT    = 0x80;
    private static final int TAR_REQ_ARCH  
        = TAR_APPEND | TAR_CREATE | TAR_CONCAT | TAR_DELETE | TAR_UPDATE | TAR_LIST | TAR_DIFF;
    private static final int TAR_INSERT     = TAR_APPEND | TAR_CREATE;
    private static final int TAR_VERIFY     = TAR_APPEND | TAR_CONCAT | TAR_DELETE | TAR_UPDATE;
    private static final int TAR_COMPRESS   = TAR_APPEND | TAR_CREATE | TAR_CONCAT | TAR_DELETE | TAR_UPDATE;
    private static final int TAR_DECOMPRESS =
        TAR_APPEND | TAR_CONCAT | TAR_DELETE | TAR_DIFF | TAR_UPDATE | TAR_LIST | TAR_EXTRACT;
   
    private static final int USE_BZIP = 1;
    private static final int USE_GZIP = 2;
   
    private final FlagArgument DoAppend  = new FlagArgument("doAppend", Argument.OPTIONAL, help_append);
    private final FlagArgument DoConcat  = new FlagArgument("doConcat", Argument.OPTIONAL, help_concat);
    private final FlagArgument DoCreate  = new FlagArgument("doCreate", Argument.OPTIONAL, help_create);
    private final FlagArgument DoDelete  = new FlagArgument("doDelete", Argument.OPTIONAL, help_delete);
    private final FlagArgument DoDiff    = new FlagArgument("doDiff", Argument.OPTIONAL, help_diff);
    private final FlagArgument DoExtract = new FlagArgument("doExtract", Argument.OPTIONAL, help_extract);
    private final FlagArgument DoList    = new FlagArgument("doList", Argument.OPTIONAL, help_list);
    private final FlagArgument DoUpdate  = new FlagArgument("doUpdate", Argument.OPTIONAL, help_update);
   
    private final FlagArgument Backup      = new FlagArgument("backup", Argument.OPTIONAL, help_backup);
    private final FlagArgument UseBzip     = new FlagArgument("bzip", Argument.OPTIONAL, help_bzip);
    private final FileArgument ChangeDir   = new FileArgument("dir", Argument.OPTIONAL, help_dir);
    private final StringArgument Exclude   = new StringArgument("exclude", Argument.OPTIONAL, help_exclude);
    private final FileArgument Archive     = new FileArgument("archive", Argument.OPTIONAL, help_archive);
    private final FileArgument FileList    = new FileArgument("fileList", Argument.OPTIONAL, help_file_list);
    private final FlagArgument UseGzip     = new FlagArgument("gzip", Argument.OPTIONAL, help_gzip);
    private final FlagArgument Interact    = new FlagArgument("interact", Argument.OPTIONAL, help_interact);
    private final FlagArgument KeepOld     = new FlagArgument("keep_old", Argument.OPTIONAL, help_keep_old);
    private final FlagArgument KeepNew     = new FlagArgument("keep_new", Argument.OPTIONAL, help_keep_new);
    private final FlagArgument NoRecurse   = new FlagArgument("noRecurse", Argument.OPTIONAL, help_norecurse);
    private final FlagArgument Recurse     = new FlagArgument("recurse", Argument.OPTIONAL, help_recurse);
    private final FlagArgument RemoveFiles = new FlagArgument("removeFiles", Argument.OPTIONAL, help_remove);
    private final FlagArgument ShowTotals  = new FlagArgument("showTotals", Argument.OPTIONAL, help_totals);
    private final StringArgument Suffix    = new StringArgument("suffix", Argument.OPTIONAL, help_suffix);
    private final FlagArgument Unlink      = new FlagArgument("unlink", Argument.OPTIONAL, help_unlink);
    private final FlagArgument Verify      = new FlagArgument("verify", Argument.OPTIONAL, help_verify);
    private final FileArgument ExcludeFile = new FileArgument("xfile", Argument.OPTIONAL, help_xfile);
   
    private final FileArgument Paths = new FileArgument("paths", Argument.OPTIONAL | Argument.MULTIPLE, help_paths);
   
    private File archive;
    @SuppressWarnings("unused")
    private File excludeFile;
    @SuppressWarnings("unused")
    private File fileList;
    private String suffix = "~";
    @SuppressWarnings("unused")
    private String exclude = "";
    private int mode;
    private int compress;
    private int decompress;
    private boolean recurse;
    @SuppressWarnings("unused")
    private boolean pipeInOut;
    private boolean backup;
    private boolean bzip;
    private boolean gzip;
    @SuppressWarnings("unused")
    private boolean interact;
    private boolean verify;
    @SuppressWarnings("unused")
    private boolean showTotals;
    private boolean keepOld;
    private boolean keepNew;
    @SuppressWarnings("unused")
    private boolean unlink;
   
    public TarCommand() {
        super("Create/Modify/Extract tape archives");
        // from ArchiveCommand
        registerArguments(Verbose, Debug, Stdout);
       
        // tar Operations
        registerArguments(DoAppend, DoConcat, DoCreate, DoDelete, DoDiff, DoExtract, DoList, DoUpdate);
       
        // tar Global Options
        registerArguments(Backup, Suffix, UseBzip, UseGzip, Archive, FileList, ExcludeFile, Interact, KeepNew, KeepOld,
                           Unlink, RemoveFiles, ShowTotals, Verify, Paths);
        // tar Parsing Options
        registerArguments(ChangeDir, Exclude, NoRecurse, Recurse);
    }
   
    // TODO Allow working directory to be changed
    public void execute() {
        super.execute("tar");
        if (!checkMode()) {
            fatal(err_options, 1);
        }
       
        if (Archive.isSet())     archive     = Archive.getValue();
        if (Suffix.isSet())      suffix      = Suffix.getValue();
        if (Exclude.isSet())     exclude     = Exclude.getValue();
        if (ExcludeFile.isSet()) excludeFile = ExcludeFile.getValue();
        if (FileList.isSet())    fileList    = FileList.getValue();
       
        backup     = Backup.isSet();
        bzip       = UseBzip.isSet();
        gzip       = UseGzip.isSet();
        interact   = Interact.isSet();
        verify     = Verify.isSet();
        showTotals = ShowTotals.isSet();
        keepOld    = KeepOld.isSet();
        keepNew    = KeepNew.isSet();
        recurse    = !NoRecurse.isSet();
        unlink     = Unlink.isSet();
       
        try {
            if ((mode & TAR_REQ_ARCH) != 0 && archive == null) {
                fatal("Archive required for -Acdtru", 1);
            }
           
            if ((mode & TAR_DECOMPRESS) != 0 && archive != null) {
                if (checkCompressed(archive) == -1) {
                    // happens when -j or -z were specified, but the archive is not
                    // in the given format.
                    if (bzip) {
                        fatal("Archive is not compressed with bzip2.", 1);
                    }
                    if (gzip) {
                        fatal("Archive is not compressed with gzip.", 1);
                    }
                    fatal("Internal Error: checkCompressed() returned -1", -1);
                }
            }
           
            if ((mode & TAR_COMPRESS) != 0) {
                if (bzip) {
                    compress = USE_BZIP;
                } else if (gzip) {
                    compress = USE_GZIP;
                } else {
                    compress = decompress;
                }
            }
           
            if ((mode & TAR_VERIFY) != 0 && verify) {
                // backup original archive
            }
           
            if ((mode & TAR_CREATE) != 0 && compress == 0) {
                compress = checkSuffix(archive);
            }
               
            if ((mode & TAR_INSERT) != 0) {
                insert(processFiles(Paths.getValues(), recurse));
            }
           
            if ((mode & TAR_UPDATE) != 0) {
                update(processFiles(Paths.getValues(), recurse));
            }
           
            if ((mode & TAR_CONCAT) != 0) {
                concat(processArchives(Paths.getValues()));
            }
           
            if ((mode & TAR_DELETE) != 0) {
                //delete();
            }
           
            if ((mode & TAR_EXTRACT) != 0) {
                if (decompress == 0 && archive == null) {
                    if (bzip) {
                        decompress = USE_BZIP;
                    } else if (gzip) {
                        decompress = USE_GZIP;
                    }
                }
                extract();
            }
           
            if ((mode & TAR_LIST) != 0) {
                list();
            }
           
            if ((mode & TAR_DIFF) != 0) {
                diff();
            }
        } catch (Exception e) {
            e.printStackTrace();
            fatal(err_exception_uncaught, 1);
        }
    }
   
    /**
     * Concatenates a list of archives with this archive.
     *
     * TODO If the verify option is set, the original archive is backed up before operating
     *      on it, and verified before exiting. If the archive is bad, the original is restored.
     */
    private void concat(File[] archives) throws IOException {
        InputStream in;
        TarInputStream tin;
        TarOutputStream tout;
       
        // Setup archive for appending
        tout = appendTarOutputStream();
       
        // Concatenate new archives
        for (File arch : archives) {
            if ((in = openFileRead(arch)) == null) {
                continue;
            }
            bzip = gzip = false;
            decompress = checkCompressed(arch);
            if (decompress != 0) {
                in = wrapInputStream(in);
            }
            tin = new TarInputStream(in);
            copy(tin, tout);
        }
        tout.close();
    }
   
    /**
     * Insert a list of files into an archive.
     *
     * This is used by Create and Append to insert new entries into the archive.
     *
     * TODO Allow files to be delete from the filesystem after the archive has been written.
     *      If the verify option is set, then the archive must pass verification before the
     *      files are deleted.
     *
     * TODO If the verify option is set, the original archive is backed up before operating
     *      on it, and verified before exiting. If the archive is bad, the original is restored.
     */
    private void insert(File[] files) throws IOException {
        InputStream in;
        OutputStream out;
        TarOutputStream tout = null;
        TarEntry entry;
       
        if (mode == TAR_APPEND && archive.exists()) {
            tout = appendTarOutputStream();
        } else {
            createArchive();
            if ((out = openFileWrite(archive, false, false)) == null) {
                fatal(" ", 1);
            }
            if (compress != 0) {
                out = wrapOutputStream(out);
            }
            tout = new TarOutputStream(out);
        }
       
        // Insert new entries
        for (File file : files) {
            notice(file.getPath());
            entry = new TarEntry(file);
            tout.putNextEntry(entry);
           
            if (!file.isDirectory()) {
                if ((in = openFileRead(file)) == null) continue;
                processStream(in, tout);
                in.close();
            }
            tout.closeEntry();
        }
        tout.close();
    }
   
    // TODO
    private void update(File[] files) throws IOException {
        InputStream in;
        TarInputStream tin;
        TarEntry entry;
        TreeMap<String, Long> entries = new TreeMap<String, Long>();
       
        if ((in = openFileRead(archive)) == null) {
            fatal(" ", 1);
        }
        if (decompress != 0) {
            in = wrapInputStream(in);
        }
       
        tin = new TarInputStream(in);
       
        while ((entry = tin.getNextEntry()) != null) {
            entries.put(entry.getName(), entry.getModTime().getTime());
        }
        tin.close();
       
        long etime, ftime;
        ArrayList<File> list = new ArrayList<File>();
        for (File file : files) {
            if (entries.containsKey(file.getPath())) {
                etime = entries.get(file.getPath());
                ftime = file.lastModified();
                if (etime >= ftime) {
                    continue;
                }
            }
            list.add(file);
        }
       
        insert(list.toArray(files));
    }
   
    // TODO
    @SuppressWarnings("unused")
    private void delete(String[] names) throws IOException {
    }
   
    /**
     * Extract entries from an archive.
     *
     * TODO Need to parse Path for choosing specific files/directories either by direct naming
     *      or by wildcard patterns.
     * TODO Read list of entries to extract from FileList if its set.
     */
    private void extract() throws IOException {
        TarEntry entry;
        InputStream in = null;
        OutputStream out;
        TarInputStream tin;
        File file;
       
        if (archive != null) {
            if ((in = openFileRead(archive)) == null) {
                fatal(" ", 1);
            }
        } else {
            in = stdin;
        }
       
        if (decompress != 0) {
            in = wrapInputStream(in);
        }
        tin = new TarInputStream(in);
       
        if (use_stdout) {
            out = stdout;
        }
       
        while ((entry = tin.getNextEntry()) != null) {
            notice(entry.getName());
            file = new File(entry.getName());
            if (entry.isDirectory()) {
                if (!file.exists()) {
                    file.mkdirs();
                }
                continue;
            } else {
                if (file.exists()) {
                    if (keepOld || (keepNew && (file.lastModified() >= entry.getModTime().getTime()))) {
                        continue;
                    }
                    if (backup) {
                        file.renameTo(new File(file.getPath() + suffix));
                    }
                }
            }
            if ((out = openFileWrite(file, true, true)) == null) {
                continue;
            }
            tin.copyEntryContents(out);
            out.close();
        }
        tin.close();
    }
   
    /**
     * List the contents of an archive.
     *
     * TODO Need to parse Path for choosing specific files/directories either by direct naming
     *      or by wildcard patterns.
     */
    private void list() throws IOException {
        TarEntry entry;
        InputStream in = null;
        TarInputStream tin;
       
        if ((in = openFileRead(archive)) == null) {
            fatal(" ", 1);
        }
       
        if (decompress != 0) {
            in = wrapInputStream(in);
        }
        tin = new TarInputStream(in);
       
        while ((entry = tin.getNextEntry()) != null) {
            out(entry.getName());
        }
    }
   
    /**
     * Outputs the differences found between the archive and the file system.
     */
    private void diff() throws IOException {
        TarEntry entry;
        InputStream in = null;
        TarInputStream tin;
        File file;
       
        if ((in = openFileRead(archive)) == null) {
            exit(1);
        }
       
        if (decompress != 0) {
            in = wrapInputStream(in);
        }
        tin = new TarInputStream(in);
       
        while ((entry = tin.getNextEntry()) != null) {
            file = new File(entry.getName());
           
            if (!file.exists()) {
                out(file + ": Warning: No such file or directory");
                continue;
            }
           
            if (file.lastModified() != entry.getModTime().getTime()) {
                out(file + ": Mod time is different");
            }
           
            if (file.length() != entry.getSize()) {
                out(file + ": Size is different");
            }
           
            // TODO check file mode
            // TODO check file ownership
        }
    }
   
    /**
     * Copies an archive to another archive.
     *
     * This is used to set an output stream into position for appending new entries,
     * and to copy entries from another archive into another archive.
     *
     * FIXME does not verify that tin is actually a tar archive (Concat)
     */
    private void copy(TarInputStream tin, TarOutputStream tout) throws IOException {
        TarEntry entry;
        while ((entry = tin.getNextEntry()) != null) {
            tout.putNextEntry(entry);
            tin.copyEntryContents(tout);
            tout.closeEntry();
        }
        tin.close();
    }
   
    /**
     * Sets up a TarOutputStream suitable for appending new entries.
     */
    private TarOutputStream appendTarOutputStream() throws IOException {
        // FIXME this isnt working.
        OutputStream out;
        InputStream in;
        TarOutputStream tout;
        TarInputStream tin;
        File tmpArchive;
       
        tmpArchive = archive.getAbsoluteFile();
        tmpArchive.renameTo(new File(archive.getName() + ".tmp"));
       
        createArchive();
       
        if ((out = openFileWrite(archive, false, false)) == null) {
            fatal(" ", 1);
        }
        if (compress != 0) {
            out = wrapOutputStream(out);
        }
        tout = new TarOutputStream(out);
       
        if ((in = openFileRead(tmpArchive)) == null) {
            fatal(" ", 1);
        }
        if (decompress != 0) {
            in = wrapInputStream(in);
        }
        tin = new TarInputStream(in);
        copy(tin, tout);
        tmpArchive.delete();
       
        return tout;
    }
       
    /**
     * Creates a file for an archive, deleting it first if it already exists.
     */
    private void createArchive() {
        try {
            if (archive.exists()) {
                archive.delete();
            }
            if (!archive.createNewFile()) {
                throw new IOException();
            }
        } catch (IOException e) {
            fatal(err_file_create + archive, 1);
        }
    }
   
    /**
     * Processes a list of files and directories given on the command line
     * in order to generate a list of files for Append, Create and Update.
     *
     * TODO Add parsing of FileList and exclusion filtering.
     */
    private File[] processFiles(File[] files , boolean recurse) {
        // FIXME object pollution
        ArrayList<File> _files = new ArrayList<File>();
       
        for (File file : files) {
            if (!file.exists()) {
                continue;
            }
            if (file.getName().equals(".") || file.getName().equals("..")) {
                continue;
            }
           
            if (file.isDirectory()) {
                if (recurse) {
                    _files.add(file);
                    Collections.addAll(_files, processFiles(file.listFiles(), recurse));
                }
                continue;
            }
            _files.add(file);
        }
       
        return _files.toArray(files);
    }
   
    // TODO Need to check that the list of files are actually archives or compressed archives.
    private File[] processArchives(File[] files) {
        return files;
    }
   
    /**
     * Wraps an InputStream with a decompression stream for reading compressed archives.
     */
    private InputStream wrapInputStream(InputStream in) throws IOException {
        if (decompress == USE_BZIP) {
            return new CBZip2InputStream(in);
        }
        if (decompress == USE_GZIP) {
            return new GZIPInputStream(in, BUFFER_SIZE);
        }
       
        fatal("Internal Error: Unknown compress type", -1);
        return null;
    }
   
    /**
     * Wraps an OutputStream with a compression stream for writing compressed archives.
     */
    private OutputStream wrapOutputStream(OutputStream out) throws IOException {
        if (compress == USE_BZIP) {
            return new CBZip2OutputStream(out);
        }
        if (compress == USE_GZIP) {
            return new GZIPOutputStream(out);
        }
       
        fatal("Internal Error: Unknown decompress type", -1);
        return null;
    }
   
    /**
     * Used by create to determine if the archive should be compressed even if the -j or -z flags
     * were not given.
     */
    private int checkSuffix(File file) {
        String name = file.getName();
        if (name.endsWith(".tbz") || name.endsWith(".tbz2") || name.endsWith(".tar.bz") || name.endsWith(".tar.bz2")) {
            return USE_BZIP;
        }
        if (name.endsWith(".tar.gz")) {
            return USE_GZIP;
        }
        return 0;
    }
   
    /**
     * Check via the file header the type of compression used to create it.
     */
    private int checkCompressed(File file) throws IOException {
        if (bzip) {
            return checkBZipMagic(file) ? USE_BZIP : -1;
        }
        if (gzip) {
            return checkGZipMagic(file) ? USE_GZIP : -1;
        }
        /*
        if (checkBZipMagic(file)) {
            return USE_BZIP;
        }
        if (checkGZipMagic(file)) {
            return USE_GZIP;
        }
        */
        return 0;
    }
   
    // TODO
    private boolean checkBZipMagic(File file) throws IOException {
        return true;
    }
   
    // TODO
    private boolean checkGZipMagic(File file) throws IOException {
        return true;
    }
   
    /**
     * Checks which operational mode was selected.
     *
     * If no mode was selected, or more than one mode was selected, the return
     * value will be false, otherwise the return value is true, and this.mode will
     * be set with the selected mode.
     */
    private boolean checkMode() {
        int check = 0;
        if (DoAppend.isSet())  {
            mode = TAR_APPEND;
            check++;
        }
        if (DoCreate.isSet())  {
            mode = TAR_CREATE;
            check++;
        }
        if (DoConcat.isSet())  {
            mode = TAR_CONCAT;
            check++;
        }
        if (DoDelete.isSet())  {
            mode = TAR_DELETE;
            check++;
        }
        if (DoDiff.isSet())    {
            mode = TAR_DIFF;
            check++;
        }
        if (DoExtract.isSet()) {
            mode = TAR_EXTRACT;
            check++;
        }
        if (DoList.isSet())    {
            mode = TAR_LIST;
            check++;
        }
        if (DoUpdate.isSet())  {
            mode = TAR_UPDATE;
            check++;
        }
        return check == 1;
    }
}
TOP

Related Classes of org.jnode.command.archive.TarCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.