/** * 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(); } }
/** * 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; }