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 <private/tracker/tracker_private.h> 27 28 #include "ZipOMaticMisc.h" 29 #include "ZipOMaticWindow.h" 30 31 32 #undef B_TRANSLATION_CONTEXT 33 #define B_TRANSLATION_CONTEXT "file:ZipperThread.cpp" 34 35 36 ZipperThread::ZipperThread(BMessage* refsMessage, BWindow* window) 37 : 38 GenericThread("ZipperThread", B_NORMAL_PRIORITY, refsMessage), 39 fWindowMessenger(window), 40 fZipProcess(-1), 41 fStdIn(-1), 42 fStdOut(-1), 43 fStdErr(-1), 44 fOutputFile(NULL) 45 { 46 fThreadDataStore = new BMessage(*refsMessage); 47 } 48 49 50 ZipperThread::~ZipperThread() 51 { 52 } 53 54 55 status_t 56 ZipperThread::ThreadStartup() 57 { 58 type_code type = B_REF_TYPE; 59 int32 refCount = 0; 60 entry_ref ref; 61 entry_ref lastRef; 62 bool sameFolder = true; 63 64 status_t status = fThreadDataStore->GetInfo("refs", &type, &refCount); 65 if (status != B_OK || refCount < 1) { 66 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 67 Quit(); 68 return B_ERROR; 69 } 70 71 for (int index = 0; index < refCount; index++) { 72 fThreadDataStore->FindRef("refs", index, &ref); 73 74 if (index > 0) { 75 if (lastRef.directory != ref.directory) { 76 sameFolder = false; 77 break; 78 } 79 } 80 lastRef = ref; 81 } 82 83 entry_ref dirRef; 84 bool gotDirRef = false; 85 86 status = fThreadDataStore->FindRef("dir_ref", 0, &dirRef); 87 if (status == B_OK) { 88 BEntry dirEntry(&dirRef); 89 BNode dirNode(&dirRef); 90 91 if (dirEntry.InitCheck() == B_OK 92 && dirEntry.Exists() 93 && dirNode.InitCheck() == B_OK 94 && dirNode.IsDirectory()) 95 gotDirRef = true; 96 } 97 98 if (gotDirRef) { 99 BEntry entry(&dirRef); 100 BPath path; 101 entry.GetPath(&path); 102 chdir(path.Path()); 103 } else if (sameFolder) { 104 BEntry entry(&lastRef); 105 BPath path; 106 entry.GetParent(&entry); 107 entry.GetPath(&path); 108 chdir(path.Path()); 109 } else { 110 BPath path; 111 if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK) 112 chdir(path.Path()); 113 } 114 115 BString archiveName; 116 117 if (refCount > 1) 118 archiveName = B_TRANSLATE("Archive"); 119 else 120 archiveName = lastRef.name; 121 122 int index = 1; 123 for (;; index++) { 124 BString tryName = archiveName; 125 126 if (index != 1) 127 tryName << " " << index; 128 129 tryName << ".zip"; 130 131 BEntry entry(tryName.String()); 132 if (!entry.Exists()) { 133 archiveName = tryName; 134 entry.GetRef(&fOutputEntryRef); 135 break; 136 } 137 } 138 139 int32 argc = refCount + 3; 140 const char** argv = new const char* [argc + 1]; 141 142 argv[0] = strdup("/bin/zip"); 143 argv[1] = strdup("-ry"); 144 argv[2] = strdup(archiveName.String()); 145 146 for (int index = 0; index < refCount; index++) { 147 fThreadDataStore->FindRef("refs", index, &ref); 148 149 if (gotDirRef || sameFolder) { 150 argv[3 + index] = strdup(ref.name); 151 } else { 152 BPath path(&ref); 153 BString file = path.Path(); 154 argv[3 + index] = strdup(path.Path()); 155 } 156 } 157 158 argv[argc] = NULL; 159 160 fZipProcess = _PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr); 161 162 delete [] argv; 163 164 if (fZipProcess < 0) 165 return fZipProcess; 166 167 resume_thread(fZipProcess); 168 169 fOutputFile = fdopen(fStdOut, "r"); 170 if (fOutputFile == NULL) 171 return errno; 172 173 _SendMessageToWindow(ZIPPO_TASK_DESCRIPTION, "archive_filename", 174 archiveName.String()); 175 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", 176 B_TRANSLATE("Preparing to archive")); 177 178 return B_OK; 179 } 180 181 182 status_t 183 ZipperThread::ExecuteUnit() 184 { 185 char buffer[4096]; 186 187 char* output = fgets(buffer, sizeof(buffer) - 1, fOutputFile); 188 if (output == NULL) 189 return EOF; 190 191 char* newLine = strrchr(output, '\n'); 192 if (newLine != NULL) 193 *newLine = '\0'; 194 195 if (!strncmp(" a", output, 3)) { 196 output[2] = 'A'; 197 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output + 2); 198 } else if (!strncmp("up", output, 2)) { 199 output[0] = 'U'; 200 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output); 201 } else { 202 _SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output); 203 } 204 205 return B_OK; 206 } 207 208 209 status_t 210 ZipperThread::ThreadShutdown() 211 { 212 close(fStdIn); 213 close(fStdOut); 214 close(fStdErr); 215 216 _SelectInTracker(); 217 218 return B_OK; 219 } 220 221 222 void 223 ZipperThread::ThreadStartupFailed(status_t status) 224 { 225 fprintf(stderr, "ZipperThread::ThreadStartupFailed(): %s\n", 226 strerror(status)); 227 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 228 229 Quit(); 230 } 231 232 233 void 234 ZipperThread::ExecuteUnitFailed(status_t status) 235 { 236 if (status == EOF) { 237 fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): EOF\n"); 238 // thread has finished, been quit or killed, we don't know 239 _SendMessageToWindow(ZIPPO_THREAD_EXIT); 240 } else { 241 fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): %s\n", 242 strerror(status)); 243 // explicit error - communicate error to Window 244 _SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR); 245 } 246 247 Quit(); 248 } 249 250 251 void 252 ZipperThread::ThreadShutdownFailed(status_t status) 253 { 254 fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n", 255 strerror(status)); 256 } 257 258 259 void 260 ZipperThread::_MakeShellSafe(BString* string) 261 { 262 string->CharacterEscape("\"$`", '\\'); 263 string->Prepend("\""); 264 string->Append("\""); 265 } 266 267 268 thread_id 269 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out, 270 int& err, const char** envp) 271 { 272 static BLocker lock; 273 274 if (lock.Lock()) { 275 // This function was originally written by Peter Folk 276 // <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ 277 // http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209 278 279 thread_id thread; 280 281 // Save current FDs 282 int oldIn = dup(STDIN_FILENO); 283 int oldOut = dup(STDOUT_FILENO); 284 int oldErr = dup(STDERR_FILENO); 285 286 int inPipe[2], outPipe[2], errPipe[2]; 287 288 // Create new pipe FDs as stdin, stdout, stderr 289 if (pipe(inPipe) < 0) 290 goto err1; 291 if (pipe(outPipe) < 0) 292 goto err2; 293 if (pipe(errPipe) < 0) 294 goto err3; 295 296 errno = 0; 297 298 // replace old stdin/stderr/stdout 299 dup2(inPipe[0], STDIN_FILENO); 300 close(inPipe[0]); 301 dup2(outPipe[1], STDOUT_FILENO); 302 close(outPipe[1]); 303 dup2(errPipe[1], STDERR_FILENO); 304 close(errPipe[1]); 305 306 if (errno == 0) { 307 in = inPipe[1]; // Write to in, appears on cmd's stdin 308 out = outPipe[0]; // Read from out, taken from cmd's stdout 309 err = errPipe[0]; // Read from err, taken from cmd's stderr 310 311 // execute command 312 thread = load_image(argc, argv, envp); 313 } else { 314 thread = errno; 315 } 316 317 // Restore old FDs 318 dup2(oldIn, STDIN_FILENO); 319 close(oldIn); 320 dup2(oldOut, STDOUT_FILENO); 321 close(oldOut); 322 dup2(oldErr, STDERR_FILENO); 323 close(oldErr); 324 325 lock.Unlock(); 326 return thread; 327 328 err3: 329 close(outPipe[0]); 330 close(outPipe[1]); 331 err2: 332 close(inPipe[0]); 333 close(inPipe[1]); 334 err1: 335 close(oldIn); 336 close(oldOut); 337 close(oldErr); 338 339 lock.Unlock(); 340 return errno; 341 } else { 342 return B_ERROR; 343 } 344 } 345 346 347 void 348 ZipperThread::_SendMessageToWindow(uint32 what, const char* name, 349 const char* value) 350 { 351 BMessage msg(what); 352 if (name != NULL && value != NULL) 353 msg.AddString(name, value); 354 355 fWindowMessenger.SendMessage(&msg); 356 } 357 358 359 status_t 360 ZipperThread::SuspendExternalZip() 361 { 362 thread_info info; 363 status_t status = get_thread_info(fZipProcess, &info); 364 365 if (status == B_OK && !strcmp(info.name, "zip")) 366 return suspend_thread(fZipProcess); 367 368 return status; 369 } 370 371 372 status_t 373 ZipperThread::ResumeExternalZip() 374 { 375 thread_info info; 376 status_t status = get_thread_info(fZipProcess, &info); 377 378 if (status == B_OK && !strcmp(info.name, "zip")) 379 return resume_thread(fZipProcess); 380 381 return status; 382 } 383 384 385 status_t 386 ZipperThread::InterruptExternalZip() 387 { 388 thread_info info; 389 status_t status = get_thread_info(fZipProcess, &info); 390 391 if (status == B_OK && !strcmp(info.name, "zip")) { 392 status = send_signal(fZipProcess, SIGINT); 393 WaitOnExternalZip(); 394 return status; 395 } 396 397 return status; 398 } 399 400 401 status_t 402 ZipperThread::WaitOnExternalZip() 403 { 404 thread_info info; 405 status_t status = get_thread_info(fZipProcess, &info); 406 407 if (status == B_OK && !strcmp(info.name, "zip")) 408 return wait_for_thread(fZipProcess, &status); 409 410 return status; 411 } 412 413 414 status_t 415 ZipperThread::_SelectInTracker() 416 { 417 entry_ref parentRef; 418 BEntry entry(&fOutputEntryRef); 419 420 if (!entry.Exists()) 421 return B_ENTRY_NOT_FOUND; 422 423 entry.GetParent(&entry); 424 entry.GetRef(&parentRef); 425 426 BMessenger trackerMessenger(kTrackerSignature); 427 if (!trackerMessenger.IsValid()) 428 return B_ERROR; 429 430 BMessage request, reply; 431 request.what = B_REFS_RECEIVED; 432 request.AddRef("refs", &parentRef); 433 status_t status = trackerMessenger.SendMessage(&request, &reply); 434 if (status != B_OK) 435 return status; 436 437 // Wait 0.3 seconds to give Tracker time to populate. 438 snooze(300000); 439 440 request.MakeEmpty(); 441 request.what = BPrivate::kSelect; 442 request.AddRef("refs", &fOutputEntryRef); 443 444 reply.MakeEmpty(); 445 return trackerMessenger.SendMessage(&request, &reply); 446 } 447