001 /* 002 * Jpkg - Java library and tools for operating system package creation. 003 * 004 * Copyright (c) 2007 Three Rings Design, Inc. 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 1. Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 2. Redistributions in binary form must reproduce the above copyright 013 * notice, this list of conditions and the following disclaimer in the 014 * documentation and/or other materials provided with the distribution. 015 * 3. Neither the name of the copyright owner nor the names of contributors 016 * may be used to endorse or promote products derived from this software 017 * without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 021 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 023 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 024 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 025 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 026 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 027 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 029 * POSSIBILITY OF SUCH DAMAGE. 030 */ 031 package com.threerings.jpkg.debian; 032 033 import java.io.ByteArrayInputStream; 034 import java.io.ByteArrayOutputStream; 035 import java.io.IOException; 036 import java.io.InputStream; 037 import java.util.Enumeration; 038 import java.util.Map.Entry; 039 import java.util.zip.GZIPOutputStream; 040 041 import javax.mail.internet.InternetHeaders; 042 043 import org.apache.commons.io.IOUtils; 044 import org.apache.tools.tar.TarEntry; 045 import org.apache.tools.tar.TarOutputStream; 046 047 import com.threerings.jpkg.PackageTarFile; 048 import com.threerings.jpkg.UnixStandardPermissions; 049 import com.threerings.jpkg.ar.ArchiveEntry; 050 051 /** 052 * Handles the creation of the Debian package control.tar.gz file. 053 */ 054 public class ControlFile 055 implements ArchiveEntry 056 { 057 /** 058 * Construct a new ControlFile which creates the contents of control.tar.gz entry in the 059 * Debian package. 060 * @param info The fully populated package meta data. 061 * @param dataTar The fully populated {@link PackageTarFile} represented by this control file. 062 * @throws IOException If any i/o exceptions occur during the control file creation. 063 * @throws ScriptDataTooLargeException If any maintainer script is too large to be added to the tar file. 064 */ 065 public ControlFile (PackageInfo info, PackageTarFile dataTar) 066 throws IOException, ScriptDataTooLargeException 067 { 068 _controlData = createTarArray(info, dataTar); 069 } 070 071 // from ArchiveEntry 072 public InputStream getInputStream () 073 { 074 return new ByteArrayInputStream(_controlData); 075 } 076 077 // from ArchiveEntry 078 public long getSize () 079 { 080 return _controlData.length; 081 } 082 083 // from ArchiveEntry 084 public String getPath () 085 { 086 return DEB_AR_CONTROL_FILE; 087 } 088 089 // from ArchiveEntry 090 public int getUserId () 091 { 092 return UnixStandardPermissions.ROOT_USER.getId(); 093 } 094 095 // from ArchiveEntry 096 public int getGroupId () 097 { 098 return UnixStandardPermissions.ROOT_GROUP.getId(); 099 } 100 101 // from ArchiveEntry 102 public int getMode () 103 { 104 return UnixStandardPermissions.STANDARD_FILE_MODE; 105 } 106 107 /** 108 * Create the control.tar.gz file as a byte array. 109 */ 110 private byte[] createTarArray (PackageInfo info, PackageTarFile dataTar) 111 throws IOException, ScriptDataTooLargeException 112 { 113 // this file will never be big, so do all the work in memory. 114 final ByteArrayOutputStream output = new ByteArrayOutputStream(); 115 final TarOutputStream controlTar = new TarOutputStream(new GZIPOutputStream(output)); 116 controlTar.setLongFileMode(TarOutputStream.LONGFILE_GNU); 117 118 try { 119 // construct the tar file. 120 addControlFile(controlTar, info, dataTar); 121 addMd5Sums(controlTar, dataTar); 122 addMaintainerScripts(controlTar, info); 123 124 } finally { 125 controlTar.close(); 126 } 127 128 return output.toByteArray(); 129 } 130 131 /** 132 * Add the control file to the tar file. 133 */ 134 private void addControlFile (TarOutputStream tar, PackageInfo info, PackageTarFile dataTar) 135 throws IOException 136 { 137 // setup the RFC822 formatted header used for package metadata. 138 final InternetHeaders headers = info.getControlHeaders(); 139 140 // add the "Installed-Size" field. 141 headers.addHeader(INSTALLED_SIZE, String.valueOf(dataTar.getTotalDataSize())); 142 143 final StringBuilder controlFile = new StringBuilder(); 144 @SuppressWarnings("unchecked") 145 final 146 Enumeration<String> en = headers.getAllHeaderLines(); 147 while (en.hasMoreElements()) 148 { 149 controlFile.append(en.nextElement()).append('\n'); 150 } 151 152 final TarEntry entry = standardEntry(DEB_CONTROL_FILE, UnixStandardPermissions.STANDARD_FILE_MODE, controlFile.length()); 153 tar.putNextEntry(entry); 154 IOUtils.write(controlFile.toString(), tar); 155 tar.closeEntry(); 156 } 157 158 /** 159 * Add the md5sums file to the tar file. 160 */ 161 private void addMd5Sums (TarOutputStream tar, PackageTarFile dataTar) 162 throws IOException 163 { 164 final StringBuilder md5sums = new StringBuilder(); 165 for (final Entry<String, String> sum : dataTar.getMd5s().entrySet()) { 166 md5sums.append(sum.getKey()).append(' ').append(sum.getValue()).append('\n'); 167 } 168 169 final TarEntry entry = standardEntry(DEB_MD5_FILE, UnixStandardPermissions.STANDARD_FILE_MODE, md5sums.length()); 170 tar.putNextEntry(entry); 171 IOUtils.write(md5sums.toString(), tar); 172 tar.closeEntry(); 173 } 174 175 /** 176 * Add the maintainer scripts to the tar file. 177 */ 178 private void addMaintainerScripts (TarOutputStream tar, PackageInfo info) 179 throws IOException, ScriptDataTooLargeException 180 { 181 for (final MaintainerScript script : info.getMaintainerScripts().values()) { 182 if (script.getSize() > Integer.MAX_VALUE) { 183 throw new ScriptDataTooLargeException( 184 "The script data is too large for the tar file. script=[" + script.getType().getFilename() + "]."); 185 } 186 187 final TarEntry entry = standardEntry(script.getType().getFilename(), UnixStandardPermissions.EXECUTABLE_FILE_MODE, (int)script.getSize()); 188 tar.putNextEntry(entry); 189 IOUtils.copy(script.getStream(), tar); 190 tar.closeEntry(); 191 } 192 } 193 194 /** 195 * Returns a TarEntry object with correct default values. 196 */ 197 private TarEntry standardEntry (String name, int mode, int size) 198 { 199 final TarEntry entry = new TarEntry(name); 200 entry.setNames(UnixStandardPermissions.ROOT_USER.getName(), UnixStandardPermissions.ROOT_GROUP.getName()); 201 entry.setIds(UnixStandardPermissions.ROOT_USER.getId(), UnixStandardPermissions.ROOT_GROUP.getId()); 202 entry.setSize(size); 203 entry.setMode(mode); 204 205 return entry; 206 } 207 208 /** The field name in the control file for the installed size of the package. */ 209 private static final String INSTALLED_SIZE = "Installed-Size"; 210 211 /** The name of the control file in the Debian package. */ 212 private static final String DEB_AR_CONTROL_FILE = "control.tar.gz"; 213 214 /** Constants for entries in the control file. */ 215 private static final String DEB_CONTROL_FILE = "control"; 216 private static final String DEB_MD5_FILE = "md5sums"; 217 218 219 /** The control.tar.gz data is held in this byte array after creation. */ 220 private final byte[] _controlData; 221 }