Mega Code Archive

 
Categories / Java / File Input Output
 

Tape Archive Lister

/*  * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.  * All rights reserved. Software written by Ian F. Darwin and others.  * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $  *  * 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.  *  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''  * AND ANY EXPRESS 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 AUTHOR OR 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.  *   * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee  * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,  * pioneering role in inventing and promulgating (and standardizing) the Java   * language and environment is gratefully acknowledged.  *   * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for  * inventing predecessor languages C and C++ is also gratefully acknowledged.  */ /* Error handling throughout. In particular, the reading code needs to checksum! Write getInputStream(). Here's how: seek on the file, malloc a buffer big enough to hold the file, read it into memory, return a ByteArrayInputStream. Handle LF_LINK linked files: getEntry(name).getOffset(). Think about writing Tar files? */ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Vector; /**  * Demonstrate the Tar archive lister.  *   * @author Ian F. Darwin, http://www.darwinsys.com/  * @version $Id: TarList.java,v 1.7 2004/03/07 17:47:35 ian Exp $  */ public class TarList {   public static void main(String[] argv) throws IOException, TarException {     if (argv.length == 0) {       System.err.println("Usage: TarList archive");       System.exit(1);     }     new TarList(argv[0]).list();   }   /** The TarFile we are reading */   TarFile tf;   /** Constructor */   public TarList(String fileName) {     tf = new TarFile(fileName);   }   /** Generate and print the listing */   public void list() throws IOException, TarException {     Enumeration list = tf.entries();     while (list.hasMoreElements()) {       TarEntry e = (TarEntry) list.nextElement();       System.out.println(toListFormat(e));     }   }   protected StringBuffer sb;   /** Shift used in formatting permissions */   protected static int[] shft = { 6, 3, 0 };   /** Format strings used in permissions */   protected static String rwx[] = { "---", "--x", "-w-", "-wx", "r--", "r-x",       "rw-", "rwx" };   /** NumberFormat used in formatting List form string */   NumberFormat sizeForm = new DecimalFormat("00000000");   /** Date used in printing mtime */   Date date = new Date();   SimpleDateFormat dateForm = new SimpleDateFormat("yyyy-MM-dd HH:mm");   /** Format a TarEntry the same way that UNIX tar does */   public String toListFormat(TarEntry e) {     sb = new StringBuffer();     switch (e.type) {     case TarEntry.LF_OLDNORMAL:     case TarEntry.LF_NORMAL:     case TarEntry.LF_CONTIG:     case TarEntry.LF_LINK: // hard link: same as file       sb.append('-'); // 'f' would be sensible       break;     case TarEntry.LF_DIR:       sb.append('d');       break;     case TarEntry.LF_SYMLINK:       sb.append('l');       break;     case TarEntry.LF_CHR: // UNIX character device file       sb.append('c');       break;     case TarEntry.LF_BLK: // UNIX block device file       sb.append('b');       break;     case TarEntry.LF_FIFO: // UNIX named pipe       sb.append('p');       break;     default: // Can't happen?       sb.append('?');       break;     }     // Convert e.g., 754 to rwxrw-r--     int mode = e.getMode();     for (int i = 0; i < 3; i++) {       sb.append(rwx[mode >> shft[i] & 007]);     }     sb.append(' ');     // owner and group     sb.append(e.getUname()).append('/').append(e.getGname()).append(' ');     // size     // DecimalFormat can't do "%-9d", so we do part of it ourselves     sb.append(' ');     String t = sizeForm.format(e.getSize());     boolean digit = false;     char c;     for (int i = 0; i < 8; i++) {       c = t.charAt(i);       if (!digit && i < (8 - 1) && c == '0')         sb.append(' '); // leading space       else {         digit = true;         sb.append(c);       }     }     sb.append(' ');     // mtime     // copy file's mtime into Data object (after scaling     // from "sec since 1970" to "msec since 1970"), and format it.     date.setTime(1000 * e.getTime());     sb.append(dateForm.format(date)).append(' ');     sb.append(e.getName());     if (e.isLink())       sb.append(" link to ").append(e.getLinkName());     if (e.isSymLink())       sb.append(" -> ").append(e.getLinkName());     return sb.toString();   } } /**  * One entry in an archive file.  *   * @author Ian Darwin  * @version $Id: TarEntry.java,v 1.7 2004/03/06 21:16:19 ian Exp $  * @note Tar format info taken from John Gilmore's public domain tar program,  * @(#)tar.h 1.21 87/05/01 Public Domain, which said: "Created 25 August 1985 by  *           John Gilmore, ihnp4!hoptoad!gnu." John is now gnu@toad.com, and by  *           another path tar.h is GPL'd in GNU Tar.  */ class TarEntry {   /** Where in the tar archive this entry's HEADER is found. */   public long fileOffset = 0;   /** The maximum size of a name */   public static final int NAMSIZ = 100;   public static final int TUNMLEN = 32;   public static final int TGNMLEN = 32;   // Next fourteen fields constitute one physical record.   // Padded to TarFile.RECORDSIZE bytes on tape/disk.   // Lazy Evaluation: just read fields in raw form, only format when asked.   /** File name */   byte[] name = new byte[NAMSIZ];   /** permissions, e.g., rwxr-xr-x? */   byte[] mode = new byte[8];   /* user */   byte[] uid = new byte[8];   /* group */   byte[] gid = new byte[8];   /* size */   byte[] size = new byte[12];   /* UNIX modification time */   byte[] mtime = new byte[12];   /* checksum field */   byte[] chksum = new byte[8];   byte type;   byte[] linkName = new byte[NAMSIZ];   byte[] magic = new byte[8];   byte[] uname = new byte[TUNMLEN];   byte[] gname = new byte[TGNMLEN];   byte[] devmajor = new byte[8];   byte[] devminor = new byte[8];   // End of the physical data fields.   /* The magic field is filled with this if uname and gname are valid. */   public static final byte TMAGIC[] = {   // 'u', 's', 't', 'a', 'r', ' ', ' ', '\0'       0, 0, 0, 0, 0, 0, 0x20, 0x20, 0 }; /* 7 chars and a null */   /* Type value for Normal file, Unix compatibility */   public static final int LF_OLDNORMAL = '\0';   /* Type value for Normal file */   public static final int LF_NORMAL = '0';   /* Type value for Link to previously dumped file */   public static final int LF_LINK = '1';   /* Type value for Symbolic link */   public static final int LF_SYMLINK = '2';   /* Type value for Character special file */   public static final int LF_CHR = '3';   /* Type value for Block special file */   public static final int LF_BLK = '4';   /* Type value for Directory */   public static final int LF_DIR = '5';   /* Type value for FIFO special file */   public static final int LF_FIFO = '6';   /* Type value for Contiguous file */   public static final int LF_CONTIG = '7';   /* Constructor that reads the entry's header. */   public TarEntry(RandomAccessFile is) throws IOException, TarException {     fileOffset = is.getFilePointer();     // read() returns -1 at EOF     if (is.read(name) < 0)       throw new EOFException();     // Tar pads to block boundary with nulls.     if (name[0] == '\0')       throw new EOFException();     // OK, read remaining fields.     is.read(mode);     is.read(uid);     is.read(gid);     is.read(size);     is.read(mtime);     is.read(chksum);     type = is.readByte();     is.read(linkName);     is.read(magic);     is.read(uname);     is.read(gname);     is.read(devmajor);     is.read(devminor);     // Since the tar header is < 512, we need to skip it.     is         .skipBytes((int) (TarFile.RECORDSIZE - (is.getFilePointer() % TarFile.RECORDSIZE)));     // TODO if checksum() fails,     //  throw new TarException("Failed to find next header");   }   /** Returns the name of the file this entry represents. */   public String getName() {     return new String(name).trim();   }   public String getTypeName() {     switch (type) {     case LF_OLDNORMAL:     case LF_NORMAL:       return "file";     case LF_LINK:       return "link w/in archive";     case LF_SYMLINK:       return "symlink";     case LF_CHR:     case LF_BLK:     case LF_FIFO:       return "special file";     case LF_DIR:       return "directory";     case LF_CONTIG:       return "contig";     default:       throw new IllegalStateException("TarEntry.getTypeName: type "           + type + " invalid");     }   }   /** Returns the UNIX-specific "mode" (type+permissions) of the entry */   public int getMode() {     try {       return Integer.parseInt(new String(mode).trim(), 8) & 0777;     } catch (IllegalArgumentException e) {       return 0;     }   }   /** Returns the size of the entry */   public int getSize() {     try {       return Integer.parseInt(new String(size).trim(), 8);     } catch (IllegalArgumentException e) {       return 0;     }   }   /**    * Returns the name of the file this entry is a link to, or null if this    * entry is not a link.    */   public String getLinkName() {     // if (isLink())     //   return null;     return new String(linkName).trim();   }   /** Returns the modification time of the entry */   public long getTime() {     try {       return Long.parseLong(new String(mtime).trim(), 8);     } catch (IllegalArgumentException e) {       return 0;     }   }   /** Returns the string name of the userid */   public String getUname() {     return new String(uname).trim();   }   /** Returns the string name of the group id */   public String getGname() {     return new String(gname).trim();   }   /** Returns the numeric userid of the entry */   public int getuid() {     try {       return Integer.parseInt(new String(uid).trim());     } catch (IllegalArgumentException e) {       return -1;     }   }   /** Returns the numeric gid of the entry */   public int getgid() {     try {       return Integer.parseInt(new String(gid).trim());     } catch (IllegalArgumentException e) {       return -1;     }   }   /** Returns true if this entry represents a file */   boolean isFile() {     return type == LF_NORMAL || type == LF_OLDNORMAL;   }   /** Returns true if this entry represents a directory */   boolean isDirectory() {     return type == LF_DIR;   }   /** Returns true if this a hard link (to a file in the archive) */   boolean isLink() {     return type == LF_LINK;   }   /** Returns true if this a symbolic link */   boolean isSymLink() {     return type == LF_SYMLINK;   }   /** Returns true if this entry represents some type of UNIX special file */   boolean isSpecial() {     return type == LF_CHR || type == LF_BLK || type == LF_FIFO;   }   public String toString() {     return "TarEntry[" + getName() + ']';   } } /*  * Exception for TarFile and TarEntry. $Id: TarException.java,v 1.3 1999/10/06  * 15:13:53 ian Exp $  */ class TarException extends java.io.IOException {   public TarException() {     super();   }   public TarException(String msg) {     super(msg);   } } /**  * Tape Archive Lister, patterned loosely after java.util.ZipFile. Since, unlike  * Zip files, there is no central directory, you have to read the entire file  * either to be sure of having a particular file's entry, or to know how many  * entries there are in the archive.  *   * @author Ian Darwin, http://www.darwinsys.com/  * @version $Id: TarFile.java,v 1.12 2004/03/07 17:53:15 ian Exp $  */ class TarFile {   /** True after we've done the expensive read. */   protected boolean read = false;   /** The list of entries found in the archive */   protected Vector list;   /** Size of header block. */   public static final int RECORDSIZE = 512;   /* Size of each block, in records */   protected int blocking;   /* Size of each block, in bytes */   protected int blocksize;   /** File containing archive */   protected String fileName;   /** Construct (open) a Tar file by name */   public TarFile(String name) {     fileName = name;     list = new Vector();   }   /** Construct (open) a Tar file by File */   public TarFile(java.io.File name) throws IOException {     this(name.getCanonicalPath());   }   /** The main datastream. */   protected RandomAccessFile is;   /**    * Read the Tar archive in its entirety. This is semi-lazy evaluation, in    * that we don't read the file until we need to. A future revision may use    * even lazier evaluation: in getEntry, scan the list and, if not found,    * continue reading! For now, just read the whole file.    */   protected void readFile() throws IOException, TarException {     is = new RandomAccessFile(fileName, "r");     TarEntry hdr;     try {       do {         hdr = new TarEntry(is);         if (hdr.getSize() < 0) {           System.out.println("Size < 0");           break;         }         // System.out.println(hdr.toString());         list.addElement(hdr);         // Get the size of the entry         int nbytes = hdr.getSize(), diff;         // Round it up to blocksize.         if ((diff = (nbytes % RECORDSIZE)) != 0) {           nbytes += RECORDSIZE - diff;         }         // And skip over the data portion.         // System.out.println("Skipping " + nbytes + " bytes");         is.skipBytes(nbytes);       } while (true);     } catch (EOFException e) {       // OK, just stop reading.     }     // All done, say we've read the contents.     read = true;   }   /* Close the Tar file. */   public void close() {     try {       is.close();     } catch (IOException e) {       // nothing to do     }   }   /* Returns an enumeration of the Tar file entries. */   public Enumeration entries() throws IOException, TarException {     if (!read) {       readFile();     }     return list.elements();   }   /** Returns the Tar entry for the specified name, or null if not found. */   public TarEntry getEntry(String name) {     for (int i = 0; i < list.size(); i++) {       TarEntry e = (TarEntry) list.elementAt(i);       if (name.equals(e.getName()))         return e;     }     return null;   }   /**    * Returns an InputStream for reading the contents of the specified entry    * from the archive. May cause the entire file to be read.    */   public InputStream getInputStream(TarEntry entry) {     return null;   }   /** Returns the path name of the Tar file. */   public String getName() {     return fileName;   }   /**    * Returns the number of entries in the Tar archive. May cause the entire    * file to be read. XXX Obviously not written yet, sorry.    */   public int size() {     return 0;   } }