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 Quit(); 224 } 225 226 227 void 228 ZipperThread::ExecuteUnitFailed(status_t status) 229 { 230 if (status == EOF) { 231 // thread has finished, been quit or killed, we don't know 232 _SendMessageToWindow(ZIPPO_THREAD_EXIT); 233 } else { 234 // explicit error - communicate error to Window 235 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 236 } 237 238 Quit(); 239 } 240 241 242 void 243 ZipperThread::ThreadShutdownFailed(status_t status) 244 { 245 fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n", 246 strerror(status)); 247 } 248 249 250 void 251 ZipperThread::_MakeShellSafe(BString* string) 252 { 253 string->CharacterEscape("\"$`", '\\'); 254 string->Prepend("\""); 255 string->Append("\""); 256 } 257 258 259 thread_id 260 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out, 261 int& err, const char** envp) 262 { 263 static BLocker lock; 264 265 if (lock.Lock()) { 266 // This function was originally written by Peter Folk 267 // <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ 268 // http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209 269 270 thread_id thread; 271 272 // Save current FDs 273 int oldIn = dup(STDIN_FILENO); 274 int oldOut = dup(STDOUT_FILENO); 275 int oldErr = dup(STDERR_FILENO); 276 277 int inPipe[2], outPipe[2], errPipe[2]; 278 279 // Create new pipe FDs as stdin, stdout, stderr 280 if (pipe(inPipe) < 0) 281 goto err1; 282 if (pipe(outPipe) < 0) 283 goto err2; 284 if (pipe(errPipe) < 0) 285 goto err3; 286 287 errno = 0; 288 289 // replace old stdin/stderr/stdout 290 dup2(inPipe[0], STDIN_FILENO); 291 close(inPipe[0]); 292 dup2(outPipe[1], STDOUT_FILENO); 293 close(outPipe[1]); 294 dup2(errPipe[1], STDERR_FILENO); 295 close(errPipe[1]); 296 297 if (errno == 0) { 298 in = inPipe[1]; // Write to in, appears on cmd's stdin 299 out = outPipe[0]; // Read from out, taken from cmd's stdout 300 err = errPipe[0]; // Read from err, taken from cmd's stderr 301 302 // execute command 303 thread = load_image(argc, argv, envp); 304 } else { 305 thread = errno; 306 } 307 308 // Restore old FDs 309 dup2(oldIn, STDIN_FILENO); 310 close(oldIn); 311 dup2(oldOut, STDOUT_FILENO); 312 close(oldOut); 313 dup2(oldErr, STDERR_FILENO); 314 close(oldErr); 315 316 lock.Unlock(); 317 return thread; 318 319 err3: 320 close(outPipe[0]); 321 close(outPipe[1]); 322 err2: 323 close(inPipe[0]); 324 close(inPipe[1]); 325 err1: 326 close(oldIn); 327 close(oldOut); 328 close(oldErr); 329 330 lock.Unlock(); 331 return errno; 332 } else { 333 return B_ERROR; 334 } 335 } 336 337 338 void 339 ZipperThread::_SendMessageToWindow(uint32 what, const char* name, 340 const char* value) 341 { 342 BMessage msg(what); 343 if (name != NULL && value != NULL) 344 msg.AddString(name, value); 345 346 fWindowMessenger.SendMessage(&msg); 347 } 348 349 350 status_t 351 ZipperThread::SuspendExternalZip() 352 { 353 thread_info info; 354 status_t status = get_thread_info(fZipProcess, &info); 355 356 if (status == B_OK && !strcmp(info.name, "zip")) 357 return suspend_thread(fZipProcess); 358 359 return status; 360 } 361 362 363 status_t 364 ZipperThread::ResumeExternalZip() 365 { 366 thread_info info; 367 status_t status = get_thread_info(fZipProcess, &info); 368 369 if (status == B_OK && !strcmp(info.name, "zip")) 370 return resume_thread(fZipProcess); 371 372 return status; 373 } 374 375 376 status_t 377 ZipperThread::InterruptExternalZip() 378 { 379 thread_info info; 380 status_t status = get_thread_info(fZipProcess, &info); 381 382 if (status == B_OK && !strcmp(info.name, "zip")) { 383 status = B_OK; 384 status = send_signal(fZipProcess, SIGINT); 385 WaitOnExternalZip(); 386 return status; 387 } 388 389 return status; 390 } 391 392 393 status_t 394 ZipperThread::WaitOnExternalZip() 395 { 396 thread_info info; 397 status_t status = get_thread_info(fZipProcess, &info); 398 399 if (status == B_OK && !strcmp(info.name, "zip")) 400 return wait_for_thread(fZipProcess, &status); 401 402 return status; 403 } 404 405 406 status_t 407 ZipperThread::_SelectInTracker(int32 tryNumber) 408 { 409 // work in progress - unreliable - not ready to be used 410 411 entry_ref parentRef; 412 BEntry entry(&fOutputEntryRef); 413 414 if (!entry.Exists()) 415 return B_FILE_NOT_FOUND; 416 417 entry.GetParent(&entry); 418 entry.GetRef(&parentRef); 419 420 BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); 421 if (!trackerMessenger.IsValid()) 422 return B_ERROR; 423 424 BMessage request; 425 BMessage reply; 426 status_t status; 427 428 if (tryNumber == 0) { 429 request.MakeEmpty(); 430 request.what = B_REFS_RECEIVED; 431 request.AddRef("refs", &parentRef); 432 trackerMessenger.SendMessage(&request, &reply); 433 } 434 435 if (tryNumber > 20) 436 return B_ERROR; 437 438 snooze(200000); 439 440 // find out the number of Tracker windows 441 request.MakeEmpty(); 442 request.what = B_COUNT_PROPERTIES; 443 request.AddSpecifier("Window"); 444 reply.MakeEmpty(); 445 446 status = trackerMessenger.SendMessage(&request, &reply); 447 if (status != B_OK) 448 return status; 449 450 int32 windowCount; 451 status = reply.FindInt32("result", &windowCount); 452 if (status != B_OK) 453 return status; 454 455 // find a likely parent window 456 bool foundWindow = false; 457 int32 index = 0; 458 for (; index < windowCount; index++) { 459 request.MakeEmpty(); 460 request.what = B_GET_PROPERTY; 461 request.AddSpecifier("Path"); 462 request.AddSpecifier("Poses"); 463 request.AddSpecifier("Window", index); 464 reply.MakeEmpty(); 465 466 status = trackerMessenger.SendMessage(&request, &reply); 467 if (status != B_OK) 468 continue; 469 470 entry_ref windowRef; 471 status = reply.FindRef("result", &windowRef); 472 if (status != B_OK) 473 continue; 474 475 if (windowRef == parentRef) { 476 foundWindow = true; 477 break; 478 } 479 } 480 481 if (!foundWindow) 482 return _SelectInTracker(tryNumber + 1); 483 484 // find entry_ref in window - a newly opened window might 485 // be filling and the entry_ref perhaps not there yet? 486 request.MakeEmpty(); 487 request.what = B_GET_PROPERTY; 488 request.AddSpecifier("Entry"); 489 request.AddSpecifier("Poses"); 490 request.AddSpecifier("Window", index); 491 reply.MakeEmpty(); 492 493 status = trackerMessenger.SendMessage(&request, &reply); 494 if (status != B_OK) 495 return _SelectInTracker(tryNumber + 1); 496 497 bool foundRef = false; 498 entry_ref ref; 499 for (int32 m = 0;; m++) { 500 status = reply.FindRef("result", m, &ref); 501 if (status != B_OK) 502 break; 503 if (ref == fOutputEntryRef) 504 foundRef = true; 505 } 506 507 // if entry_ref not found in window, start over 508 if (!foundRef) 509 return _SelectInTracker(tryNumber + 1); 510 511 // select archive file in Tracker window 512 request.MakeEmpty(); 513 request.what = B_SET_PROPERTY; 514 request.AddRef("data", &fOutputEntryRef); 515 request.AddSpecifier("Selection"); 516 request.AddSpecifier("Poses"); 517 request.AddSpecifier("Window", index); 518 reply.MakeEmpty(); 519 520 status = trackerMessenger.SendMessage(&request, &reply); 521 522 return status; 523 } 524 525