/** * Insert a list of files into an archive. * * <p>This is used by Create and Append to insert new entries into the archive. * * <p>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. * * <p>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(); }
// 循环遍历目录结构中的文件并添加至tar的输出流 public static void addFiles(TarOutputStream tout, String folderPath) { File srcPath = new File(folderPath); int length = srcPath.listFiles().length; byte[] buf = new byte[1024]; // 设定读入缓冲区尺寸 File[] files = srcPath.listFiles(); try { for (int i = 0; i < length; i++) { if (files[i].isFile()) { System.out.println("file:" + files[i].getName()); String filename = srcPath.getPath() + File.separator + files[i].getName(); // 打开需压缩文件作为文件输入流 FileInputStream fin = new FileInputStream(filename); // filename是文件全路径 TarEntry tarEn = new TarEntry(files[i]); // 此处必须使用new // TarEntry(File // file); // tarEn.setName(files[i].getName()); // //此处需重置名称,默认是带全路径的,否则打包后会带全路径 tout.putNextEntry(tarEn); int num; while ((num = fin.read(buf)) != -1) { tout.write(buf, 0, num); } tout.closeEntry(); fin.close(); } else { System.out.println(files[i].getPath()); addFiles(tout, files[i].getPath()); } } } catch (FileNotFoundException e) { System.out.println(e); } catch (IOException e) { System.out.println(e); } }
// 生成tar并压缩成tar.gz public static void WriteToTarGzip(String folderPath, String targzipFilePath) { byte[] buf = new byte[1024]; // 设定读入缓冲区尺寸 try { // 建立压缩文件输出流 FileOutputStream fout = new FileOutputStream(targzipFilePath); // 建立tar压缩输出流 TarOutputStream tout = new TarOutputStream(fout); addFiles(tout, folderPath); tout.close(); fout.close(); // 建立压缩文件输出流 FileOutputStream gzFile = new FileOutputStream(targzipFilePath + ".gz"); // 建立gzip压缩输出流 GZIPOutputStream gzout = new GZIPOutputStream(gzFile); // 打开需压缩文件作为文件输入流 FileInputStream tarin = new FileInputStream(targzipFilePath); // targzipFilePath是文件全路径 int len; while ((len = tarin.read(buf)) != -1) { gzout.write(buf, 0, len); } gzout.close(); gzFile.close(); tarin.close(); } catch (FileNotFoundException e) { System.out.println(e); } catch (IOException e) { System.out.println(e); } File tarfile = new File(targzipFilePath); tarfile.delete(); }
/** * Archive a directory. * * @param source A <code>File</code>. * @param target A <code>File</code>. * @throws IOException */ public void archive(final File source, final File target, final byte[] buffer) throws IOException { final TarOutputStream targetStream = newTargetStream(target); try { targetStream.setLongFileMode(TarOutputStream.LONGFILE_GNU); final File[] sourceFileArray = new FileSystem(source).listFiles("/", Boolean.TRUE); TarEntry tarEntry; InputStream sourceStream; for (int i = 0; i < sourceFileArray.length; i++) { tarEntry = new TarEntry(sourceFileArray[i]); targetStream.putNextEntry(tarEntry); try { sourceStream = new FileInputStream(sourceFileArray[i]); try { StreamUtil.copy(sourceStream, targetStream, buffer); } finally { sourceStream.close(); } } finally { targetStream.closeEntry(); } } } finally { targetStream.close(); } }
// 不能对每层都包含文件和目录的多层次目录结构打包 public static void compressedFiles_Gzip(String folderPath, String targzipFilePath) { File srcPath = new File(folderPath); int length = srcPath.listFiles().length; byte[] buf = new byte[1024]; // 设定读入缓冲区尺寸 File[] files = srcPath.listFiles(); try { File targetFile = new File(targzipFilePath); File parent = targetFile.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } // 建立压缩文件输出流 FileOutputStream fout = new FileOutputStream(targetFile); // 建立tar压缩输出流 TarOutputStream tout = new TarOutputStream(fout); for (int i = 0; i < length; i++) { String filename = srcPath.getPath() + File.separator + files[i].getName(); // 打开需压缩文件作为文件输入流 FileInputStream fin = new FileInputStream(filename); // filename是文件全路径 TarEntry tarEn = new TarEntry(files[i]); // 此处必须使用new // TarEntry(File // file); // tarEn.setName(files[i].getName()); // //此处需重置名称,默认是带全路径的,否则打包后会带全路径 tout.putNextEntry(tarEn); int num; while ((num = fin.read(buf)) != -1) { tout.write(buf, 0, num); } tout.closeEntry(); fin.close(); } tout.close(); fout.close(); // 建立压缩文件输出流 FileOutputStream gzFile = new FileOutputStream(targzipFilePath + ".gz"); // 建立gzip压缩输出流 GZIPOutputStream gzout = new GZIPOutputStream(gzFile); // 打开需压缩文件作为文件输入流 FileInputStream tarin = new FileInputStream(targzipFilePath); // targzipFilePath是文件全路径 int len; while ((len = tarin.read(buf)) != -1) { gzout.write(buf, 0, len); } gzout.close(); gzFile.close(); tarin.close(); // 因为只要tar.gz文件,所以删除.tar文件 del(targzipFilePath); } catch (FileNotFoundException e) { System.out.println(e); } catch (IOException e) { System.out.println(e); } }
/** * Copies an archive to another archive. * * <p>This is used to set an output stream into position for appending new entries, and to copy * entries from another archive into another archive. * * <p>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(); }
private static void addEntry( final String pName, final String pContent, final TarOutputStream pOutput) throws IOException { final byte[] data = pContent.getBytes("UTF-8"); final TarEntry entry = new TarEntry("./" + pName); entry.setSize(data.length); entry.setNames("root", "root"); pOutput.putNextEntry(entry); pOutput.write(data); pOutput.closeEntry(); }
/** * Concatenates a list of archives with this archive. * * <p>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(); }
@Test(timeout = 30000) public void testUnTar() throws IOException { setupDirs(); // make a simple tar: final File simpleTar = new File(del, FILE); OutputStream os = new FileOutputStream(simpleTar); TarOutputStream tos = new TarOutputStream(os); try { TarEntry te = new TarEntry("/bar/foo"); byte[] data = "some-content".getBytes("UTF-8"); te.setSize(data.length); tos.putNextEntry(te); tos.write(data); tos.closeEntry(); tos.flush(); tos.finish(); } finally { tos.close(); } // successfully untar it into an existing dir: FileUtil.unTar(simpleTar, tmp); // check result: assertTrue(new File(tmp, "/bar/foo").exists()); assertEquals(12, new File(tmp, "/bar/foo").length()); final File regularFile = new File(tmp, "QuickBrownFoxJumpsOverTheLazyDog"); regularFile.createNewFile(); assertTrue(regularFile.exists()); try { FileUtil.unTar(simpleTar, regularFile); assertTrue("An IOException expected.", false); } catch (IOException ioe) { // okay } }
/** * Build the data archive of the deb from the provided DataProducers * * @param pData * @param pOutput * @param pChecksums * @param pCompression the compression method used for the data file (gzip, bzip2 or anything else * for no compression) * @return * @throws NoSuchAlgorithmException * @throws IOException */ BigInteger buildData( final DataProducer[] pData, final File pOutput, final StringBuffer pChecksums, String pCompression) throws NoSuchAlgorithmException, IOException { OutputStream out = new FileOutputStream(pOutput); if ("gzip".equals(pCompression)) { out = new GZIPOutputStream(out); } else if ("bzip2".equals(pCompression)) { out.write("BZ".getBytes()); out = new CBZip2OutputStream(out); } final TarOutputStream outputStream = new TarOutputStream(out); outputStream.setLongFileMode(TarOutputStream.LONGFILE_GNU); final MessageDigest digest = MessageDigest.getInstance("MD5"); final Total dataSize = new Total(); final List addedDirectories = new ArrayList(); final DataConsumer receiver = new DataConsumer() { public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size) throws IOException { dirname = fixPath(dirname); createParentDirectories((new File(dirname)).getParent(), user, uid, group, gid); // The directory passed in explicitly by the caller also gets the passed-in mode. // (Unlike // the parent directories for now. See related comments at "int mode =" in // createParentDirectories, including about a possible bug.) createDirectory(dirname, user, uid, group, gid, mode, 0); console.println("dir: " + dirname); } public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size) throws IOException { filename = fixPath(filename); createParentDirectories(fixPath(new File(filename).getParent()), user, uid, group, gid); TarEntry entry = new TarEntry(filename); // FIXME: link is in the constructor entry.setUserName(user); entry.setUserId(uid); entry.setGroupName(group); entry.setGroupId(gid); entry.setMode(mode); entry.setSize(size); outputStream.putNextEntry(entry); dataSize.add(size); digest.reset(); Utils.copy(inputStream, new DigestOutputStream(outputStream, digest)); final String md5 = Utils.toHex(digest.digest()); outputStream.closeEntry(); console.println( "file:" + entry.getName() + " size:" + entry.getSize() + " mode:" + entry.getMode() + " linkname:" + entry.getLinkName() + " username:"******" userid:" + entry.getUserId() + " groupname:" + entry.getGroupName() + " groupid:" + entry.getGroupId() + " modtime:" + entry.getModTime() + " md5: " + md5); pChecksums.append(md5).append(" ").append(entry.getName()).append('\n'); } private String fixPath(String path) { // If we're receiving directory names from Windows, then we'll convert to use slash // This does eliminate the ability to use of a backslash in a directory name on *NIX, // but in practice, this is a non-issue if (path.indexOf('\\') > -1) { path = path.replace('\\', '/'); } // ensure the path is like : ./foo/bar if (path.startsWith("/")) { path = "." + path; } else if (!path.startsWith("./")) { path = "./" + path; } // remove double slashes in order to get unique file/folder names path = path.replaceAll("\\/\\/", "\\/"); return path; } private void createDirectory( String directory, String user, int uid, String group, int gid, int mode, long size) throws IOException { // All dirs should end with "/" when created, or the test // DebAndTaskTestCase.testTarFileSet() thinks its a file // and so thinks it has the wrong permission. // This consistency also helps when checking if a directory already exists in // addedDirectories. if (!directory.endsWith("/")) { directory += "/"; } if (!addedDirectories.contains(directory)) { TarEntry entry = new TarEntry(directory); // FIXME: link is in the constructor entry.setUserName(user); entry.setUserId(uid); entry.setGroupName(group); entry.setGroupId(gid); entry.setMode(mode); entry.setSize(size); outputStream.putNextEntry(entry); outputStream.closeEntry(); addedDirectories.add( directory); // so addedDirectories consistently have "/" for finding duplicates. } } private void createParentDirectories( String dirname, String user, int uid, String group, int gid) throws IOException { // Debian packages must have parent directories created // before sub-directories or files can be installed. // For example, if an entry of ./usr/lib/foo/bar existed // in a .deb package, but the ./usr/lib/foo directory didn't // exist, the package installation would fail. The .deb must // then have an entry for ./usr/lib/foo and then ./usr/lib/foo/bar if (dirname == null) { return; } // The loop below will create entries for all parent directories // to ensure that .deb packages will install correctly. String[] pathParts = dirname.split("\\/"); String parentDir = "./"; for (int i = 1; i < pathParts.length; i++) { parentDir += pathParts[i] + "/"; // Make it so the dirs can be traversed by users. // We could instead try something more granular, like setting the directory // permission to 'rx' for each of the 3 user/group/other read permissions // found on the file being added (ie, only if "other" has read // permission on the main node, then add o+rx permission on all the containing // directories, same w/ user & group), and then also we'd have to // check the parentDirs collection of those already added to // see if those permissions need to be similarly updated. (Note, it hasn't // been demonstrated, but there might be a bug if a user specifically // requests a directory with certain permissions, // that has already been auto-created because it was a parent, and if so, go set // the user-requested mode on that directory instead of this automatic one.) // But for now, keeping it simple by making every dir a+rx. Examples are: // drw-r----- fs/fs # what you get with setMode(mode) // drwxr-xr-x fs/fs # Usable. Too loose? int mode = TarEntry.DEFAULT_DIR_MODE; createDirectory(parentDir, user, uid, group, gid, mode, 0); } } }; for (int i = 0; i < pData.length; i++) { final DataProducer data = pData[i]; data.produce(receiver); } outputStream.close(); console.println("Total size: " + dataSize); return dataSize.count; }
/** * Build control archive of the deb * * @param pControlFiles * @param pDataSize * @param pChecksums * @param pOutput * @return * @throws FileNotFoundException * @throws IOException * @throws ParseException */ private PackageDescriptor buildControl( final File[] pControlFiles, final BigInteger pDataSize, final StringBuffer pChecksums, final File pOutput) throws IOException, ParseException { PackageDescriptor packageDescriptor = null; final TarOutputStream outputStream = new TarOutputStream(new GZIPOutputStream(new FileOutputStream(pOutput))); outputStream.setLongFileMode(TarOutputStream.LONGFILE_GNU); for (int i = 0; i < pControlFiles.length; i++) { final File file = pControlFiles[i]; if (file.isDirectory()) { continue; } final TarEntry entry = new TarEntry(file); final String name = file.getName(); entry.setName("./" + name); entry.setNames("root", "root"); entry.setMode(PermMapper.toMode("755")); if ("control".equals(name)) { packageDescriptor = new PackageDescriptor(new FileInputStream(file), resolver); if (packageDescriptor.get("Date") == null) { SimpleDateFormat fmt = new SimpleDateFormat( "EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH); // Mon, 26 Mar 2007 11:44:04 +0200 (RFC 2822) // FIXME Is this field allowed in package descriptors ? packageDescriptor.set("Date", fmt.format(new Date())); } if (packageDescriptor.get("Distribution") == null) { packageDescriptor.set("Distribution", "unknown"); } if (packageDescriptor.get("Urgency") == null) { packageDescriptor.set("Urgency", "low"); } final String debFullName = System.getenv("DEBFULLNAME"); final String debEmail = System.getenv("DEBEMAIL"); if (debFullName != null && debEmail != null) { packageDescriptor.set("Maintainer", debFullName + " <" + debEmail + ">"); console.println("Using maintainer from the environment variables."); } continue; } final InputStream inputStream = new FileInputStream(file); outputStream.putNextEntry(entry); Utils.copy(inputStream, outputStream); outputStream.closeEntry(); inputStream.close(); } if (packageDescriptor == null) { throw new FileNotFoundException("No control file in " + Arrays.toString(pControlFiles)); } packageDescriptor.set("Installed-Size", pDataSize.divide(BigInteger.valueOf(1024)).toString()); addEntry("control", packageDescriptor.toString(), outputStream); addEntry("md5sums", pChecksums.toString(), outputStream); outputStream.close(); return packageDescriptor; }