1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.vafer.jdeb;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.math.BigInteger;
27 import java.security.DigestOutputStream;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.text.ParseException;
31 import java.text.SimpleDateFormat;
32 import java.util.Arrays;
33 import java.util.Date;
34 import java.util.Locale;
35 import java.util.zip.GZIPOutputStream;
36
37 import org.apache.tools.bzip2.CBZip2OutputStream;
38 import org.apache.tools.tar.TarEntry;
39 import org.apache.tools.tar.TarOutputStream;
40 import org.vafer.jdeb.ar.ArEntry;
41 import org.vafer.jdeb.ar.ArOutputStream;
42 import org.vafer.jdeb.changes.ChangeSet;
43 import org.vafer.jdeb.changes.ChangesProvider;
44 import org.vafer.jdeb.descriptors.ChangesDescriptor;
45 import org.vafer.jdeb.descriptors.InvalidDescriptorException;
46 import org.vafer.jdeb.descriptors.PackageDescriptor;
47 import org.vafer.jdeb.signing.SigningUtils;
48 import org.vafer.jdeb.utils.InformationOutputStream;
49 import org.vafer.jdeb.utils.Utils;
50 import org.vafer.jdeb.utils.VariableResolver;
51
52
53
54
55
56
57
58 public class Processor {
59
60 private final Console console;
61 private final VariableResolver resolver;
62
63 private static final class Total {
64 private BigInteger count = BigInteger.valueOf(0);
65
66 public void add(long size) {
67 count = count.add(BigInteger.valueOf(size));
68 }
69
70 public String toString() {
71 return "" + count;
72 }
73
74 public BigInteger toBigInteger() {
75 return count;
76 }
77 }
78
79 public Processor( final Console pConsole, final VariableResolver pResolver ) {
80 console = pConsole;
81 resolver = pResolver;
82 }
83
84 private void addTo( final ArOutputStream pOutput, final String pName, final String pContent ) throws IOException {
85 final byte[] content = pContent.getBytes();
86 pOutput.putNextEntry(new ArEntry(pName, content.length));
87 pOutput.write(content);
88 }
89
90 private void addTo( final ArOutputStream pOutput, final String pName, final File pContent ) throws IOException {
91 pOutput.putNextEntry(new ArEntry(pName, pContent.length()));
92
93 final InputStream input = new FileInputStream(pContent);
94 try {
95 Utils.copy(input, pOutput);
96 } finally {
97 input.close();
98 }
99 }
100
101
102
103
104
105
106
107
108
109
110
111 public PackageDescriptor createDeb( final File[] pControlFiles, final DataProducer[] pData, final File pOutput, String compression ) throws PackagingException, InvalidDescriptorException {
112
113 File tempData = null;
114 File tempControl = null;
115
116 try {
117 tempData = File.createTempFile("deb", "data");
118 tempControl = File.createTempFile("deb", "control");
119
120 console.println("Building data");
121 final StringBuffer md5s = new StringBuffer();
122 final BigInteger size = buildData(pData, tempData, md5s, compression);
123
124 console.println("Building control");
125 final PackageDescriptor packageDescriptor = buildControl(pControlFiles, size, md5s, tempControl);
126
127 if (!packageDescriptor.isValid()) {
128 throw new InvalidDescriptorException(packageDescriptor);
129 }
130
131 final InformationOutputStream output = new InformationOutputStream(new FileOutputStream(pOutput), MessageDigest.getInstance("MD5"));
132
133 final ArOutputStream ar = new ArOutputStream(output);
134
135 addTo(ar, "debian-binary", "2.0\n");
136 addTo(ar, "control.tar.gz", tempControl);
137 addTo(ar, "data.tar" + getExtension(compression), tempData);
138
139 ar.close();
140
141
142 packageDescriptor.set("MD5", output.getMd5());
143 packageDescriptor.set("Size", "" + output.getSize());
144 packageDescriptor.set("File", pOutput.getName());
145
146 return packageDescriptor;
147
148 } catch(InvalidDescriptorException e) {
149 throw e;
150 } catch(Exception e) {
151 throw new PackagingException("Could not create deb package", e);
152 } finally {
153 if (tempData != null) {
154 if (!tempData.delete()) {
155 throw new PackagingException("Could not delete " + tempData);
156 }
157 }
158 if (tempControl != null) {
159 if (!tempControl.delete()) {
160 throw new PackagingException("Could not delete " + tempControl);
161 }
162 }
163 }
164 }
165
166
167
168
169
170
171
172 private String getExtension( final String pCompression ) {
173 if ("gzip".equals(pCompression)) {
174 return ".gz";
175 } else if ("bzip2".equals(pCompression)) {
176 return ".bz2";
177 } else {
178 return "";
179 }
180 }
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 public ChangesDescriptor createChanges( final PackageDescriptor pPackageDescriptor, final ChangesProvider pChangesProvider, final InputStream pRing, final String pKey, final String pPassphrase, final OutputStream pOutput ) throws IOException, InvalidDescriptorException {
196
197 final ChangeSet[] changeSets = pChangesProvider.getChangesSets();
198 final ChangesDescriptor changesDescriptor = new ChangesDescriptor(pPackageDescriptor, changeSets);
199
200 changesDescriptor.set("Format", "1.7");
201
202 if (changesDescriptor.get("Binary") == null) {
203 changesDescriptor.set("Binary", changesDescriptor.get("Package"));
204 }
205
206 if (changesDescriptor.get("Source") == null) {
207 changesDescriptor.set("Source", changesDescriptor.get("Package"));
208 }
209
210 if (changesDescriptor.get("Description") == null) {
211 changesDescriptor.set("Description", "update to " + changesDescriptor.get("Version"));
212 }
213
214 final StringBuffer files = new StringBuffer("\n");
215 files.append(' ').append(changesDescriptor.get("MD5"));
216 files.append(' ').append(changesDescriptor.get("Size"));
217 files.append(' ').append(changesDescriptor.get("Section"));
218 files.append(' ').append(changesDescriptor.get("Priority"));
219 files.append(' ').append(changesDescriptor.get("File"));
220 changesDescriptor.set("Files", files.toString());
221
222 if (!changesDescriptor.isValid()) {
223 throw new InvalidDescriptorException(changesDescriptor);
224 }
225
226 final String changes = changesDescriptor.toString();
227
228
229 final byte[] changesBytes = changes.getBytes("UTF-8");
230
231 if (pRing == null || pKey == null || pPassphrase == null) {
232 pOutput.write(changesBytes);
233 pOutput.close();
234 return changesDescriptor;
235 }
236
237 console.println("Signing changes with key " + pKey);
238
239 final InputStream input = new ByteArrayInputStream(changesBytes);
240
241 try {
242 SigningUtils.clearSign(input, pRing, pKey, pPassphrase, pOutput);
243 } catch (Exception e) {
244 e.printStackTrace();
245 }
246
247 pOutput.close();
248
249 return changesDescriptor;
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263 private PackageDescriptor buildControl( final File[] pControlFiles, final BigInteger pDataSize, final StringBuffer pChecksums, final File pOutput ) throws IOException, ParseException {
264
265 PackageDescriptor packageDescriptor = null;
266
267 final TarOutputStream outputStream = new TarOutputStream(new GZIPOutputStream(new FileOutputStream(pOutput)));
268 outputStream.setLongFileMode(TarOutputStream.LONGFILE_GNU);
269
270 for (int i = 0; i < pControlFiles.length; i++) {
271 final File file = pControlFiles[i];
272
273 if (file.isDirectory()) {
274 continue;
275 }
276
277 final TarEntry entry = new TarEntry(file);
278
279 final String name = file.getName();
280
281 entry.setName(name);
282
283 if ("control".equals(name)) {
284 packageDescriptor = new PackageDescriptor(new FileInputStream(file), resolver);
285
286 if (packageDescriptor.get("Date") == null) {
287 SimpleDateFormat fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
288
289 packageDescriptor.set("Date", fmt.format(new Date()));
290 }
291
292 if (packageDescriptor.get("Distribution") == null) {
293 packageDescriptor.set("Distribution", "unknown");
294 }
295
296 if (packageDescriptor.get("Urgency") == null) {
297 packageDescriptor.set("Urgency", "low");
298 }
299
300 final String debFullName = System.getenv("DEBFULLNAME");
301 final String debEmail = System.getenv("DEBEMAIL");
302
303 if (debFullName != null && debEmail != null) {
304 packageDescriptor.set("Maintainer", debFullName + " <" + debEmail + ">");
305 console.println("Using maintainer from the environment variables.");
306 }
307
308 continue;
309 }
310
311 final InputStream inputStream = new FileInputStream(file);
312
313 outputStream.putNextEntry(entry);
314
315 Utils.copy(inputStream, outputStream);
316
317 outputStream.closeEntry();
318
319 inputStream.close();
320
321 }
322
323 if (packageDescriptor == null) {
324 throw new FileNotFoundException("No control file in " + Arrays.toString(pControlFiles));
325 }
326
327 packageDescriptor.set("Installed-Size", pDataSize.divide(BigInteger.valueOf(1024)).toString());
328
329 addEntry("control", packageDescriptor.toString(), outputStream);
330
331 addEntry("md5sums", pChecksums.toString(), outputStream);
332
333 outputStream.close();
334
335 return packageDescriptor;
336 }
337
338
339
340
341
342
343
344
345
346
347
348 private BigInteger buildData( final DataProducer[] pData, final File pOutput, final StringBuffer pChecksums, String pCompression ) throws NoSuchAlgorithmException, IOException {
349
350 OutputStream out = new FileOutputStream(pOutput);
351 if ("gzip".equals(pCompression)) {
352 out = new GZIPOutputStream(out);
353 } else if ("bzip2".equals(pCompression)) {
354 out.write("BZ".getBytes());
355 out = new CBZip2OutputStream(out);
356 }
357
358 final TarOutputStream outputStream = new TarOutputStream(out);
359 outputStream.setLongFileMode(TarOutputStream.LONGFILE_GNU);
360
361 final MessageDigest digest = MessageDigest.getInstance("MD5");
362
363 final Total dataSize = new Total();
364
365 final DataConsumer receiver = new DataConsumer() {
366 public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
367
368 if (!dirname.endsWith("/")) {
369 dirname = dirname + "/";
370 }
371
372 if (!dirname.startsWith("/")) {
373 dirname = "/" + dirname;
374 }
375
376 TarEntry entry = new TarEntry(dirname);
377
378
379 entry.setUserName(user);
380 entry.setUserId(uid);
381 entry.setGroupName(group);
382 entry.setGroupId(gid);
383 entry.setMode(mode);
384 entry.setSize(0);
385
386 outputStream.putNextEntry(entry);
387
388 console.println("dir: " + dirname);
389
390 outputStream.closeEntry();
391 }
392
393 public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException {
394
395 if (!filename.startsWith("/")) {
396 filename = "/" + filename;
397 }
398
399 TarEntry entry = new TarEntry(filename);
400
401
402 entry.setUserName(user);
403 entry.setUserId(uid);
404 entry.setGroupName(group);
405 entry.setGroupId(gid);
406 entry.setMode(mode);
407 entry.setSize(size);
408
409 outputStream.putNextEntry(entry);
410
411 dataSize.add(size);
412
413 digest.reset();
414
415 Utils.copy(inputStream, new DigestOutputStream(outputStream, digest));
416
417 final String md5 = Utils.toHex(digest.digest());
418
419 outputStream.closeEntry();
420
421 console.println(
422 "file:" + entry.getName() +
423 " size:" + entry.getSize() +
424 " mode:" + entry.getMode() +
425 " linkname:" + entry.getLinkName() +
426 " username:" + entry.getUserName() +
427 " userid:" + entry.getUserId() +
428 " groupname:" + entry.getGroupName() +
429 " groupid:" + entry.getGroupId() +
430 " modtime:" + entry.getModTime() +
431 " md5: " + md5
432 );
433
434 pChecksums.append(md5).append(" ").append(entry.getName()).append('\n');
435
436 }
437 };
438
439 for (int i = 0; i < pData.length; i++) {
440 final DataProducer data = pData[i];
441 data.produce(receiver);
442 }
443
444 outputStream.close();
445
446 console.println("Total size: " + dataSize);
447
448 return dataSize.count;
449 }
450
451 private static void addEntry( final String pName, final String pContent, final TarOutputStream pOutput ) throws IOException {
452 final byte[] data = pContent.getBytes("UTF-8");
453
454 final TarEntry entry = new TarEntry(pName);
455 entry.setSize(data.length);
456
457 pOutput.putNextEntry(entry);
458 pOutput.write(data);
459 pOutput.closeEntry();
460 }
461
462
463 }