final File dir = output.getParentFile();
if (dir != null && (!dir.exists() || !dir.isDirectory())) {
throw new IOException("Cannot write data file at '" + output.getAbsolutePath() + "'");
}
final TarArchiveOutputStream tarOutputStream = new TarArchiveOutputStream(compression.toCompressedOutputStream(new FileOutputStream(output)));
tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
final MessageDigest digest = MessageDigest.getInstance("MD5");
final Total dataSize = new Total();
final List<String> addedDirectories = new ArrayList<String>();
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 {
// Check link name
checkField(linkname, TarConstants.NAMELEN);
// Check user name
checkField(user, TarConstants.UNAMELEN);
// Check group name
checkField(group, TarConstants.GNAMELEN);
dirname = fixPath(dirname);
createParentDirectories(dirname, 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.debug("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 {
// Check link name
checkField(linkname, TarConstants.NAMELEN);
// Check user name
checkField(user, TarConstants.UNAMELEN);
// Check group name
checkField(group, TarConstants.GNAMELEN);
filename = fixPath(filename);
createParentDirectories(filename, user, uid, group, gid);
final TarArchiveEntry entry = new TarArchiveEntry(filename, true);
entry.setUserName(user);
entry.setUserId(uid);
entry.setGroupName(group);
entry.setGroupId(gid);
entry.setMode(mode);
entry.setSize(size);
tarOutputStream.putArchiveEntry(entry);
dataSize.add(size);
digest.reset();
Utils.copy(inputStream, new DigestOutputStream(tarOutputStream, digest));
final String md5 = Utils.toHex(digest.digest());
tarOutputStream.closeArchiveEntry();
console.debug(
"file:" + entry.getName() +
" size:" + entry.getSize() +
" mode:" + entry.getMode() +
" linkname:" + entry.getLinkName() +
" username:" + entry.getUserName() +
" userid:" + entry.getUserId() +
" groupname:" + entry.getGroupName() +
" groupid:" + entry.getGroupId() +
" modtime:" + entry.getModTime() +
" md5: " + md5
);
// append to file md5 list, two spaces to be compatible with GNU coreutils md5sum
checksums.append(md5).append(" ").append(entry.getName()).append('\n');
}
public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException {
// Check link name
checkField(linkname, TarConstants.NAMELEN);
// Check user name
checkField(user, TarConstants.UNAMELEN);
// Check group name
checkField(group, TarConstants.GNAMELEN);
path = fixPath(path);
createParentDirectories(path, user, uid, group, gid);
final TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK);
entry.setLinkName(linkname);
entry.setUserName(user);
entry.setUserId(uid);
entry.setGroupName(group);
entry.setGroupId(gid);
entry.setMode(mode);
tarOutputStream.putArchiveEntry(entry);
tarOutputStream.closeArchiveEntry();
console.debug(
"link:" + entry.getName() +
" mode:" + entry.getMode() +
" linkname:" + entry.getLinkName() +
" username:" + entry.getUserName() +
" userid:" + entry.getUserId() +
" groupname:" + entry.getGroupName() +
" groupid:" + entry.getGroupId()
);
}
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)) {
TarArchiveEntry entry = new TarArchiveEntry(directory, true);
entry.setUserName(user);
entry.setUserId(uid);
entry.setGroupName(group);
entry.setGroupId(gid);
entry.setMode(mode);
entry.setSize(size);
tarOutputStream.putArchiveEntry(entry);
tarOutputStream.closeArchiveEntry();
addedDirectories.add(directory); // so addedDirectories consistently have "/" for finding duplicates.
}
}
private void createParentDirectories( String filename, String user, int uid, String group, int gid ) throws IOException {
String dirname = fixPath(new File(filename).getParent());
// 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 = TarArchiveEntry.DEFAULT_DIR_MODE;
createDirectory(parentDir, user, uid, group, gid, mode, 0);
}
}
};
try {
for (DataProducer data : producers) {
data.produce(receiver);
}
} finally {
tarOutputStream.close();
}
console.debug("Total size: " + dataSize);
return dataSize.count;