1/*
2 * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>
3 *  All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include "UnzipEngine.h"
7
8#include <new>
9
10#include <stdio.h>
11#include <string.h>
12
13#include <Directory.h>
14#include <Entry.h>
15#include <File.h>
16#include <Node.h>
17#include <Path.h>
18#include <String.h>
19
20#include "CommandPipe.h"
21#include "SemaphoreLocker.h"
22#include "ProgressReporter.h"
23
24
25using std::nothrow;
26
27
28UnzipEngine::UnzipEngine(ProgressReporter* reporter,
29		sem_id cancelSemaphore)
30	:
31	fPackage(""),
32	fRetrievingListing(false),
33
34	fBytesToUncompress(0),
35	fBytesUncompressed(0),
36	fLastBytesUncompressed(0),
37	fItemsToUncompress(0),
38	fItemsUncompressed(0),
39	fLastItemsUncompressed(0),
40
41	fProgressReporter(reporter),
42	fCancelSemaphore(cancelSemaphore)
43{
44}
45
46
47UnzipEngine::~UnzipEngine()
48{
49}
50
51
52status_t
53UnzipEngine::SetTo(const char* pathToPackage, const char* destinationFolder)
54{
55	fPackage = pathToPackage;
56	fDestinationFolder = destinationFolder;
57
58	fEntrySizeMap.Clear();
59
60	fBytesToUncompress = 0;
61	fBytesUncompressed = 0;
62	fLastBytesUncompressed = 0;
63	fItemsToUncompress = 0;
64	fItemsUncompressed = 0;
65	fLastItemsUncompressed = 0;
66
67	BPrivate::BCommandPipe commandPipe;
68	status_t ret = commandPipe.AddArg("unzip");
69	if (ret == B_OK)
70		ret = commandPipe.AddArg("-l");
71	if (ret == B_OK)
72		ret = commandPipe.AddArg(fPackage.String());
73	if (ret != B_OK)
74		return ret;
75
76	// Launch the unzip thread and start reading the stdout and stderr output.
77	FILE* stdOutAndErrPipe = NULL;
78	thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe);
79	if (unzipThread < 0)
80		return (status_t)unzipThread;
81
82	fRetrievingListing = true;
83	ret = commandPipe.ReadLines(stdOutAndErrPipe, this);
84	fRetrievingListing = false;
85
86	printf("%" B_PRIu64 " items in %" B_PRIdOFF " bytes\n", fItemsToUncompress,
87		fBytesToUncompress);
88
89	return ret;
90}
91
92
93status_t
94UnzipEngine::UnzipPackage()
95{
96	if (fItemsToUncompress == 0)
97		return B_NO_INIT;
98
99	BPrivate::BCommandPipe commandPipe;
100	status_t ret = commandPipe.AddArg("unzip");
101	if (ret == B_OK)
102		ret = commandPipe.AddArg("-o");
103	if (ret == B_OK)
104		ret = commandPipe.AddArg(fPackage.String());
105	if (ret == B_OK)
106		ret = commandPipe.AddArg("-d");
107	if (ret == B_OK)
108		ret = commandPipe.AddArg(fDestinationFolder.String());
109	if (ret != B_OK) {
110		fprintf(stderr, "Faild to construct argument list for unzip "
111			"process: %s\n", strerror(ret));
112		return ret;
113	}
114
115	// Launch the unzip thread and start reading the stdout and stderr output.
116	FILE* stdOutAndErrPipe = NULL;
117	thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe);
118	if (unzipThread < 0)
119		return (status_t)unzipThread;
120
121	ret = commandPipe.ReadLines(stdOutAndErrPipe, this);
122	if (ret != B_OK) {
123		fprintf(stderr, "Piping the unzip process failed: %s\n",
124			strerror(ret));
125		return ret;
126	}
127
128	// Add the contents of a potentially existing .OptionalPackageDescription
129	// to the COPYRIGHTS attribute of AboutSystem.
130	BPath descriptionPath(fDestinationFolder.String(),
131		".OptionalPackageDescription");
132	ret = descriptionPath.InitCheck();
133	if (ret != B_OK) {
134		fprintf(stderr, "Failed to construct path to "
135			".OptionalPackageDescription: %s\n", strerror(ret));
136		return ret;
137	}
138
139	BEntry descriptionEntry(descriptionPath.Path());
140	if (!descriptionEntry.Exists())
141		return B_OK;
142
143	BFile descriptionFile(&descriptionEntry, B_READ_ONLY);
144	ret = descriptionFile.InitCheck();
145	if (ret != B_OK) {
146		fprintf(stderr, "Failed to construct file to "
147			".OptionalPackageDescription: %s\n", strerror(ret));
148		return ret;
149	}
150
151	BPath aboutSystemPath(fDestinationFolder.String(),
152		"system/apps/AboutSystem");
153	ret = aboutSystemPath.InitCheck();
154	if (ret != B_OK) {
155		fprintf(stderr, "Failed to construct path to AboutSystem: %s\n",
156			strerror(ret));
157		return ret;
158	}
159
160	BNode aboutSystemNode(aboutSystemPath.Path());
161	ret = aboutSystemNode.InitCheck();
162	if (ret != B_OK) {
163		fprintf(stderr, "Failed to construct node to AboutSystem: %s\n",
164			strerror(ret));
165		return ret;
166	}
167
168	const char* kCopyrightsAttrName = "COPYRIGHTS";
169
170	BString copyrightAttr;
171	ret = aboutSystemNode.ReadAttrString(kCopyrightsAttrName, &copyrightAttr);
172	if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) {
173		fprintf(stderr, "Failed to read current COPYRIGHTS attribute from "
174			"AboutSystem: %s\n", strerror(ret));
175		return ret;
176	}
177
178	// Append the contents of the current optional package description to
179	// the existing COPYRIGHTS attribute from AboutSystem
180	size_t bufferSize = 2048;
181	char buffer[bufferSize + 1];
182	buffer[bufferSize] = '\0';
183	while (true) {
184		ssize_t read = descriptionFile.Read(buffer, bufferSize);
185		if (read > 0) {
186			int32 length = copyrightAttr.Length();
187			if (read < (ssize_t)bufferSize)
188				buffer[read] = '\0';
189			int32 bufferLength = strlen(buffer);
190				// Should be "read", but maybe we have a zero in the
191				// buffer in which case the next check would be fooled.
192			copyrightAttr << buffer;
193			if (copyrightAttr.Length() != length + bufferLength) {
194				fprintf(stderr, "Failed to append buffer to COPYRIGHTS "
195					"attribute.\n");
196				return B_NO_MEMORY;
197			}
198		} else
199			break;
200	}
201
202	if (copyrightAttr[copyrightAttr.Length() - 1] != '\n')
203		copyrightAttr << '\n\n';
204	else
205		copyrightAttr << '\n';
206
207	ret = aboutSystemNode.WriteAttrString(kCopyrightsAttrName, &copyrightAttr);
208	if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) {
209		fprintf(stderr, "Failed to read current COPYRIGHTS attribute from "
210			"AboutSystem: %s\n", strerror(ret));
211		return ret;
212	}
213
214	// Don't leave the .OptionalPackageDescription behind.
215	descriptionFile.Unset();
216	descriptionEntry.Remove();
217
218	return B_OK;
219}
220
221
222// #pragma mark -
223
224
225bool
226UnzipEngine::IsCanceled()
227{
228	if (fCancelSemaphore < 0)
229		return false;
230
231	SemaphoreLocker locker(fCancelSemaphore);
232	return !locker.IsLocked();
233}
234
235
236status_t
237UnzipEngine::ReadLine(const BString& line)
238{
239	if (fRetrievingListing)
240		return _ReadLineListing(line);
241	else
242		return _ReadLineExtract(line);
243}
244
245
246status_t
247UnzipEngine::_ReadLineListing(const BString& line)
248{
249	static const char* kListingFormat = "%llu  %s %s   %s\n";
250
251	const char* string = line.String();
252	while (string[0] == ' ')
253		string++;
254
255	uint64 bytes;
256	char date[16];
257	char time[16];
258	char path[1024];
259	if (sscanf(string, kListingFormat, &bytes, &date, &time, &path) == 4) {
260		fBytesToUncompress += bytes;
261
262		BString itemPath(path);
263		BString itemName(path);
264		int leafPos = itemPath.FindLast('/');
265		if (leafPos >= 0)
266			itemName = itemPath.String() + leafPos + 1;
267
268		// We check if the target folder exists and don't increment
269		// the item count in that case. Unzip won't report on folders that did
270		// not need to be created. This may mess up our current item count.
271		uint32 itemCount = 1;
272		if (bytes == 0 && itemName.Length() == 0) {
273			// a folder?
274			BPath destination(fDestinationFolder.String());
275			if (destination.Append(itemPath.String()) == B_OK) {
276				BEntry test(destination.Path());
277				if (test.Exists() && test.IsDirectory()) {
278//					printf("ignoring %s\n", itemPath.String());
279					itemCount = 0;
280				}
281			}
282		}
283
284		fItemsToUncompress += itemCount;
285
286//		printf("item %s with %llu bytes to %s\n", itemName.String(),
287//			bytes, itemPath.String());
288
289		fEntrySizeMap.Put(itemName.String(), bytes);
290	} else {
291//		printf("listing not understood: %s", string);
292	}
293
294	return B_OK;
295}
296
297
298status_t
299UnzipEngine::_ReadLineExtract(const BString& line)
300{
301	const char* kCreatingFormat = "   creating:";
302	const char* kInflatingFormat = "  inflating:";
303	const char* kLinkingFormat = "    linking:";
304	if (line.FindFirst(kCreatingFormat) == 0
305		|| line.FindFirst(kInflatingFormat) == 0
306		|| line.FindFirst(kLinkingFormat) == 0) {
307
308		fItemsUncompressed++;
309
310		BString itemPath;
311
312		int pos = line.FindLast(" -> ");
313		if (pos > 0)
314			line.CopyInto(itemPath, 13, pos - 13);
315		else
316			line.CopyInto(itemPath, 13, line.CountChars() - 14);
317
318		itemPath.Trim();
319		pos = itemPath.FindLast('/');
320		BString itemName = itemPath.String() + pos + 1;
321		itemPath.Truncate(pos);
322
323		off_t bytes = 0;
324		if (fEntrySizeMap.ContainsKey(itemName.String())) {
325			bytes = fEntrySizeMap.Get(itemName.String());
326			fBytesUncompressed += bytes;
327		}
328
329//		printf("%llu extracted %s to %s (%llu)\n", fItemsUncompressed,
330//			itemName.String(), itemPath.String(), bytes);
331
332		_UpdateProgress(itemName.String(), itemPath.String());
333	} else {
334//		printf("ignored: '%s'\n", line.String());
335	}
336
337	return B_OK;
338}
339
340
341void
342UnzipEngine::_UpdateProgress(const char* item, const char* targetFolder)
343{
344	if (fProgressReporter == NULL)
345		return;
346
347	uint64 items = 0;
348	if (fLastItemsUncompressed < fItemsUncompressed) {
349		items = fItemsUncompressed - fLastItemsUncompressed;
350		fLastItemsUncompressed = fItemsUncompressed;
351	}
352
353	off_t bytes = 0;
354	if (fLastBytesUncompressed < fBytesUncompressed) {
355		bytes = fBytesUncompressed - fLastBytesUncompressed;
356		fLastBytesUncompressed = fBytesUncompressed;
357	}
358
359	fProgressReporter->ItemsWritten(items, bytes, item, targetFolder);
360}
361