1 /* 2 * Copyright 2003-2009, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Jonas Sundström, jonas@kirilla.com 7 * Peter Folk <pfolk@uni.uiuc.edu> 8 */ 9 10 11 #include "ZipperThread.h" 12 13 #include <errno.h> 14 #include <signal.h> 15 #include <string.h> 16 #include <unistd.h> 17 18 #include <Catalog.h> 19 #include <FindDirectory.h> 20 #include <Locale.h> 21 #include <Locker.h> 22 #include <Message.h> 23 #include <Path.h> 24 #include <Volume.h> 25 26 #include "ZipOMaticMisc.h" 27 #include "ZipOMaticWindow.h" 28 29 30 #undef B_TRANSLATION_CONTEXT 31 #define B_TRANSLATION_CONTEXT "file:ZipperThread.cpp" 32 33 34 ZipperThread::ZipperThread(BMessage* refsMessage, BWindow* window) 35 : 36 GenericThread("ZipperThread", B_NORMAL_PRIORITY, refsMessage), 37 fWindowMessenger(window), 38 fZipProcess(-1), 39 fStdIn(-1), 40 fStdOut(-1), 41 fStdErr(-1), 42 fOutputFile(NULL) 43 { 44 fThreadDataStore = new BMessage(*refsMessage); 45 } 46 47 48 ZipperThread::~ZipperThread() 49 { 50 } 51 52 53 status_t 54 ZipperThread::ThreadStartup() 55 { 56 type_code type = B_REF_TYPE; 57 int32 refCount = 0; 58 entry_ref ref; 59 entry_ref lastRef; 60 bool sameFolder = true; 61 62 status_t status = fThreadDataStore->GetInfo("refs", &type, &refCount); 63 if (status != B_OK || refCount < 1) { 64 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 65 Quit(); 66 return B_ERROR; 67 } 68 69 for (int index = 0; index < refCount; index++) { 70 fThreadDataStore->FindRef("refs", index, &ref); 71 72 if (index > 0) { 73 if (lastRef.directory != ref.directory) { 74 sameFolder = false; 75 break; 76 } 77 } 78 lastRef = ref; 79 } 80 81 entry_ref dirRef; 82 bool gotDirRef = false; 83 84 status = fThreadDataStore->FindRef("dir_ref", 0, &dirRef); 85 if (status == B_OK) { 86 BEntry dirEntry(&dirRef); 87 BNode dirNode(&dirRef); 88 89 if (dirEntry.InitCheck() == B_OK 90 && dirEntry.Exists() 91 && dirNode.InitCheck() == B_OK 92 && dirNode.IsDirectory()) 93 gotDirRef = true; 94 } 95 96 if (gotDirRef) { 97 BEntry entry(&dirRef); 98 BPath path; 99 entry.GetPath(&path); 100 chdir(path.Path()); 101 } else if (sameFolder) { 102 BEntry entry(&lastRef); 103 BPath path; 104 entry.GetParent(&entry); 105 entry.GetPath(&path); 106 chdir(path.Path()); 107 } else { 108 BPath path; 109 if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK) 110 chdir(path.Path()); 111 } 112 113 BString archiveName; 114 115 if (refCount > 1) 116 archiveName = B_TRANSLATE("Archive"); 117 else 118 archiveName = lastRef.name; 119 120 int index = 1; 121 for (;; index++) { 122 BString tryName = archiveName; 123 124 if (index != 1) 125 tryName << " " << index; 126 127 tryName << ".zip"; 128 129 BEntry entry(tryName.String()); 130 if (!entry.Exists()) { 131 archiveName = tryName; 132 entry.GetRef(&fOutputEntryRef); 133 break; 134 } 135 } 136 137 int32 argc = refCount + 3; 138 const char** argv = new const char* [argc + 1]; 139 140 argv[0] = strdup("/bin/zip"); 141 argv[1] = strdup("-ry"); 142 argv[2] = strdup(archiveName.String()); 143 144 for (int index = 0; index < refCount; index++) { 145 fThreadDataStore->FindRef("refs", index, &ref); 146 147 if (gotDirRef || sameFolder) { 148 argv[3 + index] = strdup(ref.name); 149 } else { 150 BPath path(&ref); 151 BString file = path.Path(); 152 argv[3 + index] = strdup(path.Path()); 153 } 154 } 155 156 argv[argc] = NULL; 157 158 fZipProcess = _PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr); 159 160 delete [] argv; 161 162 if (fZipProcess < 0) 163 return fZipProcess; 164 165 resume_thread(fZipProcess); 166 167 fOutputFile = fdopen(fStdOut, "r"); 168 if (fOutputFile == NULL) 169 return errno; 170 171 _SendMessageToWindow(ZIPPO_TASK_DESCRIPTION, "archive_filename", 172 archiveName.String()); 173 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", 174 B_TRANSLATE("Preparing to archive")); 175 176 return B_OK; 177 } 178 179 180 status_t 181 ZipperThread::ExecuteUnit() 182 { 183 char buffer[4096]; 184 185 char* output = fgets(buffer, sizeof(buffer) - 1, fOutputFile); 186 if (output == NULL) 187 return EOF; 188 189 char* newLine = strrchr(output, '\n'); 190 if (newLine != NULL) 191 *newLine = '\0'; 192 193 if (!strncmp(" a", output, 3)) { 194 output[2] = 'A'; 195 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output + 2); 196 } else if (!strncmp("up", output, 2)) { 197 output[0] = 'U'; 198 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output); 199 } else { 200 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output); 201 } 202 203 return B_OK; 204 } 205 206 207 status_t 208 ZipperThread::ThreadShutdown() 209 { 210 close(fStdIn); 211 close(fStdOut); 212 close(fStdErr); 213 214 // _SelectInTracker(); 215 216 return B_OK; 217 } 218 219 220 void 221 ZipperThread::ThreadStartupFailed(status_t status) 222 { 223 fprintf(stderr, "ZipperThread::ThreadStartupFailed(): %s\n", 224 strerror(status)); 225 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 226 227 Quit(); 228 } 229 230 231 void 232 ZipperThread::ExecuteUnitFailed(status_t status) 233 { 234 if (status == EOF) { 235 fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): EOF\n"); 236 // thread has finished, been quit or killed, we don't know 237 _SendMessageToWindow(ZIPPO_THREAD_EXIT); 238 } else { 239 fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): %s\n", 240 strerror(status)); 241 // explicit error - communicate error to Window 242 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 243 } 244 245 Quit(); 246 } 247 248 249 void 250 ZipperThread::ThreadShutdownFailed(status_t status) 251 { 252 fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n", 253 strerror(status)); 254 } 255 256 257 void 258 ZipperThread::_MakeShellSafe(BString* string) 259 { 260 string->CharacterEscape("\"$`", '\\'); 261 string->Prepend("\""); 262 string->Append("\""); 263 } 264 265 266 thread_id 267 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out, 268 int& err, const char** envp) 269 { 270 static BLocker lock; 271 272 if (lock.Lock()) { 273 // This function was originally written by Peter Folk 274 // <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ 275 // http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209 276 277 thread_id thread; 278 279 // Save current FDs 280 int oldIn = dup(STDIN_FILENO); 281 int oldOut = dup(STDOUT_FILENO); 282 int oldErr = dup(STDERR_FILENO); 283 284 int inPipe[2], outPipe[2], errPipe[2]; 285 286 // Create new pipe FDs as stdin, stdout, stderr 287 if (pipe(inPipe) < 0) 288 goto err1; 289 if (pipe(outPipe) < 0) 290 goto err2; 291 if (pipe(errPipe) < 0) 292 goto err3; 293 294 errno = 0; 295 296 // replace old stdin/stderr/stdout 297 dup2(inPipe[0], STDIN_FILENO); 298 close(inPipe[0]); 299 dup2(outPipe[1], STDOUT_FILENO); 300 close(outPipe[1]); 301 dup2(errPipe[1], STDERR_FILENO); 302 close(errPipe[1]); 303 304 if (errno == 0) { 305 in = inPipe[1]; // Write to in, appears on cmd's stdin 306 out = outPipe[0]; // Read from out, taken from cmd's stdout 307 err = errPipe[0]; // Read from err, taken from cmd's stderr 308 309 // execute command 310 thread = load_image(argc, argv, envp); 311 } else { 312 thread = errno; 313 } 314 315 // Restore old FDs 316 dup2(oldIn, STDIN_FILENO); 317 close(oldIn); 318 dup2(oldOut, STDOUT_FILENO); 319 close(oldOut); 320 dup2(oldErr, STDERR_FILENO); 321 close(oldErr); 322 323 lock.Unlock(); 324 return thread; 325 326 err3: 327 close(outPipe[0]); 328 close(outPipe[1]); 329 err2: 330 close(inPipe[0]); 331 close(inPipe[1]); 332 err1: 333 close(oldIn); 334 close(oldOut); 335 close(oldErr); 336 337 lock.Unlock(); 338 return errno; 339 } else { 340 return B_ERROR; 341 } 342 } 343 344 345 void 346 ZipperThread::_SendMessageToWindow(uint32 what, const char* name, 347 const char* value) 348 { 349 BMessage msg(what); 350 if (name != NULL && value != NULL) 351 msg.AddString(name, value); 352 353 fWindowMessenger.SendMessage(&msg); 354 } 355 356 357 status_t 358 ZipperThread::SuspendExternalZip() 359 { 360 thread_info info; 361 status_t status = get_thread_info(fZipProcess, &info); 362 363 if (status == B_OK && !strcmp(info.name, "zip")) 364 return suspend_thread(fZipProcess); 365 366 return status; 367 } 368 369 370 status_t 371 ZipperThread::ResumeExternalZip() 372 { 373 thread_info info; 374 status_t status = get_thread_info(fZipProcess, &info); 375 376 if (status == B_OK && !strcmp(info.name, "zip")) 377 return resume_thread(fZipProcess); 378 379 return status; 380 } 381 382 383 status_t 384 ZipperThread::InterruptExternalZip() 385 { 386 thread_info info; 387 status_t status = get_thread_info(fZipProcess, &info); 388 389 if (status == B_OK && !strcmp(info.name, "zip")) { 390 status = B_OK; 391 status = send_signal(fZipProcess, SIGINT); 392 WaitOnExternalZip(); 393 return status; 394 } 395 396 return status; 397 } 398 399 400 status_t 401 ZipperThread::WaitOnExternalZip() 402 { 403 thread_info info; 404 status_t status = get_thread_info(fZipProcess, &info); 405 406 if (status == B_OK && !strcmp(info.name, "zip")) 407 return wait_for_thread(fZipProcess, &status); 408 409 return status; 410 } 411 412 413 status_t 414 ZipperThread::_SelectInTracker(int32 tryNumber) 415 { 416 // work in progress - unreliable - not ready to be used 417 418 entry_ref parentRef; 419 BEntry entry(&fOutputEntryRef); 420 421 if (!entry.Exists()) 422 return B_FILE_NOT_FOUND; 423 424 entry.GetParent(&entry); 425 entry.GetRef(&parentRef); 426 427 BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); 428 if (!trackerMessenger.IsValid()) 429 return B_ERROR; 430 431 BMessage request; 432 BMessage reply; 433 status_t status; 434 435 if (tryNumber == 0) { 436 request.MakeEmpty(); 437 request.what = B_REFS_RECEIVED; 438 request.AddRef("refs", &parentRef); 439 trackerMessenger.SendMessage(&request, &reply); 440 } 441 442 if (tryNumber > 20) 443 return B_ERROR; 444 445 snooze(200000); 446 447 // find out the number of Tracker windows 448 request.MakeEmpty(); 449 request.what = B_COUNT_PROPERTIES; 450 request.AddSpecifier("Window"); 451 reply.MakeEmpty(); 452 453 status = trackerMessenger.SendMessage(&request, &reply); 454 if (status != B_OK) 455 return status; 456 457 int32 windowCount; 458 status = reply.FindInt32("result", &windowCount); 459 if (status != B_OK) 460 return status; 461 462 // find a likely parent window 463 bool foundWindow = false; 464 int32 index = 0; 465 for (; index < windowCount; index++) { 466 request.MakeEmpty(); 467 request.what = B_GET_PROPERTY; 468 request.AddSpecifier("Path"); 469 request.AddSpecifier("Poses"); 470 request.AddSpecifier("Window", index); 471 reply.MakeEmpty(); 472 473 status = trackerMessenger.SendMessage(&request, &reply); 474 if (status != B_OK) 475 continue; 476 477 entry_ref windowRef; 478 status = reply.FindRef("result", &windowRef); 479 if (status != B_OK) 480 continue; 481 482 if (windowRef == parentRef) { 483 foundWindow = true; 484 break; 485 } 486 } 487 488 if (!foundWindow) 489 return _SelectInTracker(tryNumber + 1); 490 491 // find entry_ref in window - a newly opened window might 492 // be filling and the entry_ref perhaps not there yet? 493 request.MakeEmpty(); 494 request.what = B_GET_PROPERTY; 495 request.AddSpecifier("Entry"); 496 request.AddSpecifier("Poses"); 497 request.AddSpecifier("Window", index); 498 reply.MakeEmpty(); 499 500 status = trackerMessenger.SendMessage(&request, &reply); 501 if (status != B_OK) 502 return _SelectInTracker(tryNumber + 1); 503 504 bool foundRef = false; 505 entry_ref ref; 506 for (int32 m = 0;; m++) { 507 status = reply.FindRef("result", m, &ref); 508 if (status != B_OK) 509 break; 510 if (ref == fOutputEntryRef) 511 foundRef = true; 512 } 513 514 // if entry_ref not found in window, start over 515 if (!foundRef) 516 return _SelectInTracker(tryNumber + 1); 517 518 // select archive file in Tracker window 519 request.MakeEmpty(); 520 request.what = B_SET_PROPERTY; 521 request.AddRef("data", &fOutputEntryRef); 522 request.AddSpecifier("Selection"); 523 request.AddSpecifier("Poses"); 524 request.AddSpecifier("Window", index); 525 reply.MakeEmpty(); 526 527 status = trackerMessenger.SendMessage(&request, &reply); 528 529 return status; 530 } 531 532