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 = send_signal(fZipProcess, SIGINT); 391 WaitOnExternalZip(); 392 return status; 393 } 394 395 return status; 396 } 397 398 399 status_t 400 ZipperThread::WaitOnExternalZip() 401 { 402 thread_info info; 403 status_t status = get_thread_info(fZipProcess, &info); 404 405 if (status == B_OK && !strcmp(info.name, "zip")) 406 return wait_for_thread(fZipProcess, &status); 407 408 return status; 409 } 410 411 412 status_t 413 ZipperThread::_SelectInTracker(int32 tryNumber) 414 { 415 // work in progress - unreliable - not ready to be used 416 417 entry_ref parentRef; 418 BEntry entry(&fOutputEntryRef); 419 420 if (!entry.Exists()) 421 return B_FILE_NOT_FOUND; 422 423 entry.GetParent(&entry); 424 entry.GetRef(&parentRef); 425 426 BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); 427 if (!trackerMessenger.IsValid()) 428 return B_ERROR; 429 430 BMessage request; 431 BMessage reply; 432 status_t status; 433 434 if (tryNumber == 0) { 435 request.MakeEmpty(); 436 request.what = B_REFS_RECEIVED; 437 request.AddRef("refs", &parentRef); 438 trackerMessenger.SendMessage(&request, &reply); 439 } 440 441 if (tryNumber > 20) 442 return B_ERROR; 443 444 snooze(200000); 445 446 // find out the number of Tracker windows 447 request.MakeEmpty(); 448 request.what = B_COUNT_PROPERTIES; 449 request.AddSpecifier("Window"); 450 reply.MakeEmpty(); 451 452 status = trackerMessenger.SendMessage(&request, &reply); 453 if (status != B_OK) 454 return status; 455 456 int32 windowCount; 457 status = reply.FindInt32("result", &windowCount); 458 if (status != B_OK) 459 return status; 460 461 // find a likely parent window 462 bool foundWindow = false; 463 int32 index = 0; 464 for (; index < windowCount; index++) { 465 request.MakeEmpty(); 466 request.what = B_GET_PROPERTY; 467 request.AddSpecifier("Path"); 468 request.AddSpecifier("Poses"); 469 request.AddSpecifier("Window", index); 470 reply.MakeEmpty(); 471 472 status = trackerMessenger.SendMessage(&request, &reply); 473 if (status != B_OK) 474 continue; 475 476 entry_ref windowRef; 477 status = reply.FindRef("result", &windowRef); 478 if (status != B_OK) 479 continue; 480 481 if (windowRef == parentRef) { 482 foundWindow = true; 483 break; 484 } 485 } 486 487 if (!foundWindow) 488 return _SelectInTracker(tryNumber + 1); 489 490 // find entry_ref in window - a newly opened window might 491 // be filling and the entry_ref perhaps not there yet? 492 request.MakeEmpty(); 493 request.what = B_GET_PROPERTY; 494 request.AddSpecifier("Entry"); 495 request.AddSpecifier("Poses"); 496 request.AddSpecifier("Window", index); 497 reply.MakeEmpty(); 498 499 status = trackerMessenger.SendMessage(&request, &reply); 500 if (status != B_OK) 501 return _SelectInTracker(tryNumber + 1); 502 503 bool foundRef = false; 504 entry_ref ref; 505 for (int32 m = 0;; m++) { 506 status = reply.FindRef("result", m, &ref); 507 if (status != B_OK) 508 break; 509 if (ref == fOutputEntryRef) 510 foundRef = true; 511 } 512 513 // if entry_ref not found in window, start over 514 if (!foundRef) 515 return _SelectInTracker(tryNumber + 1); 516 517 // select archive file in Tracker window 518 request.MakeEmpty(); 519 request.what = B_SET_PROPERTY; 520 request.AddRef("data", &fOutputEntryRef); 521 request.AddSpecifier("Selection"); 522 request.AddSpecifier("Poses"); 523 request.AddSpecifier("Window", index); 524 reply.MakeEmpty(); 525 526 status = trackerMessenger.SendMessage(&request, &reply); 527 528 return status; 529 } 530 531