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
25 using std::nothrow;
26
27
UnzipEngine(ProgressReporter * reporter,sem_id cancelSemaphore)28 UnzipEngine::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
~UnzipEngine()47 UnzipEngine::~UnzipEngine()
48 {
49 }
50
51
52 status_t
SetTo(const char * pathToPackage,const char * destinationFolder)53 UnzipEngine::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
93 status_t
UnzipPackage()94 UnzipEngine::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, ©rightAttr);
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, ©rightAttr);
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
225 bool
IsCanceled()226 UnzipEngine::IsCanceled()
227 {
228 if (fCancelSemaphore < 0)
229 return false;
230
231 SemaphoreLocker locker(fCancelSemaphore);
232 return !locker.IsLocked();
233 }
234
235
236 status_t
ReadLine(const BString & line)237 UnzipEngine::ReadLine(const BString& line)
238 {
239 if (fRetrievingListing)
240 return _ReadLineListing(line);
241 else
242 return _ReadLineExtract(line);
243 }
244
245
246 status_t
_ReadLineListing(const BString & line)247 UnzipEngine::_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
298 status_t
_ReadLineExtract(const BString & line)299 UnzipEngine::_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
341 void
_UpdateProgress(const char * item,const char * targetFolder)342 UnzipEngine::_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