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 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 47 UnzipEngine::~UnzipEngine() 48 { 49 } 50 51 52 status_t 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("%llu items in %llu bytes\n", fItemsToUncompress, 87 fBytesToUncompress); 88 89 return ret; 90 } 91 92 93 status_t 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 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 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 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 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 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